gina 0.3.9 → 0.3.10

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.
Files changed (391) hide show
  1. package/.github/scripts/scan-vendored-cves.js +1 -1
  2. package/.github/workflows/bundle-freshness.yml +3 -3
  3. package/.github/workflows/security.yml +52 -0
  4. package/.github/workflows/vendored-cve.yml +2 -2
  5. package/CHANGELOG.md +12 -4
  6. package/README.md +5 -8
  7. package/ROADMAP.md +1 -0
  8. package/framework/v0.3.10/VERSION +1 -0
  9. package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/js/gina.js +396 -295
  10. package/framework/v0.3.10/core/asset/plugin/dist/vendor/gina/js/gina.min.js +548 -0
  11. package/framework/v0.3.10/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
  12. package/framework/v0.3.10/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
  13. package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/session-store.v4.js +1 -1
  14. package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.js +19 -1
  15. package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.render-nunjucks.js +8 -8
  16. package/framework/{v0.3.9 → v0.3.10}/core/gna.js +1 -1
  17. package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/session/src/main.js +2 -2
  18. package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/validator/src/main.js +388 -292
  19. package/framework/{v0.3.9 → v0.3.10}/core/server.isaac.js +16 -0
  20. package/framework/{v0.3.9 → v0.3.10}/core/server.js +4 -4
  21. package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/migrate.js +1 -1
  22. package/framework/{v0.3.9 → v0.3.10}/lib/cmd/inspector/help.txt +2 -2
  23. package/framework/{v0.3.9 → v0.3.10}/lib/collection/src/main.js +8 -3
  24. package/framework/{v0.3.9 → v0.3.10}/package.json +1 -1
  25. package/gna.js +4 -4
  26. package/llms.txt +21 -7
  27. package/package.json +3 -2
  28. package/resources/git-hooks/pre-commit +4 -1
  29. package/script/_load_private_tokens.js +77 -0
  30. package/script/check_no_local_leak.js +49 -21
  31. package/script/retry_lockfile_sync.js +85 -0
  32. package/framework/v0.3.9/VERSION +0 -1
  33. package/framework/v0.3.9/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -545
  34. package/framework/v0.3.9/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
  35. package/framework/v0.3.9/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
  36. /package/framework/{v0.3.9 → v0.3.10}/AUTHORS +0 -0
  37. /package/framework/{v0.3.9 → v0.3.10}/LICENSE +0 -0
  38. /package/framework/{v0.3.9 → v0.3.10}/core/asset/html/nolayout.html +0 -0
  39. /package/framework/{v0.3.9 → v0.3.10}/core/asset/html/static.html +0 -0
  40. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/android-chrome-192x192.png +0 -0
  41. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/android-chrome-512x512.png +0 -0
  42. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/apple-touch-icon.png +0 -0
  43. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/favicon-16x16.png +0 -0
  44. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/favicon-32x32.png +0 -0
  45. /package/framework/{v0.3.9 → v0.3.10}/core/asset/img/favicon.ico +0 -0
  46. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/README.md +0 -0
  47. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
  48. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
  49. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
  50. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
  51. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
  52. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
  53. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
  54. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
  55. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
  56. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
  57. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
  58. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
  59. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
  60. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
  61. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
  62. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
  63. /package/framework/{v0.3.9 → v0.3.10}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
  64. /package/framework/{v0.3.9 → v0.3.10}/core/config.js +0 -0
  65. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/ai/index.js +0 -0
  66. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/ai/lib/connector.js +0 -0
  67. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/index.js +0 -0
  68. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/connector.js +0 -0
  69. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/connector.v2.js +0 -0
  70. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/connector.v3.js +0 -0
  71. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/connector.v4.js +0 -0
  72. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/n1ql.js +0 -0
  73. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/session-store.js +0 -0
  74. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/session-store.v2.js +0 -0
  75. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/couchbase/lib/session-store.v3.js +0 -0
  76. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/mysql/index.js +0 -0
  77. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/mysql/lib/connector.js +0 -0
  78. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/postgresql/index.js +0 -0
  79. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/postgresql/lib/connector.js +0 -0
  80. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/redis/index.js +0 -0
  81. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/redis/lib/session-store.js +0 -0
  82. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/sql-parser.js +0 -0
  83. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/sqlite/index.js +0 -0
  84. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/sqlite/lib/connector.js +0 -0
  85. /package/framework/{v0.3.9 → v0.3.10}/core/connectors/sqlite/lib/session-store.js +0 -0
  86. /package/framework/{v0.3.9 → v0.3.10}/core/content.encoding +0 -0
  87. /package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.framework.js +0 -0
  88. /package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.render-json.js +0 -0
  89. /package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.render-stream.js +0 -0
  90. /package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.render-swig.js +0 -0
  91. /package/framework/{v0.3.9 → v0.3.10}/core/controller/controller.render-v1.js +0 -0
  92. /package/framework/{v0.3.9 → v0.3.10}/core/controller/index.js +0 -0
  93. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/LICENSE +0 -0
  94. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/README.md +0 -0
  95. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/lib/index.js +0 -0
  96. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/lib/types/multipart.js +0 -0
  97. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/lib/types/urlencoded.js +0 -0
  98. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/lib/utils.js +0 -0
  99. /package/framework/{v0.3.9 → v0.3.10}/core/deps/busboy-1.6.0/package.json +0 -0
  100. /package/framework/{v0.3.9 → v0.3.10}/core/deps/streamsearch-1.1.0/LICENSE +0 -0
  101. /package/framework/{v0.3.9 → v0.3.10}/core/deps/streamsearch-1.1.0/lib/sbmh.js +0 -0
  102. /package/framework/{v0.3.9 → v0.3.10}/core/deps/streamsearch-1.1.0/package.json +0 -0
  103. /package/framework/{v0.3.9 → v0.3.10}/core/dev/index.js +0 -0
  104. /package/framework/{v0.3.9 → v0.3.10}/core/dev/lib/class.js +0 -0
  105. /package/framework/{v0.3.9 → v0.3.10}/core/dev/lib/factory.js +0 -0
  106. /package/framework/{v0.3.9 → v0.3.10}/core/dev/lib/tools.js +0 -0
  107. /package/framework/{v0.3.9 → v0.3.10}/core/locales/README.md +0 -0
  108. /package/framework/{v0.3.9 → v0.3.10}/core/locales/currency.json +0 -0
  109. /package/framework/{v0.3.9 → v0.3.10}/core/locales/dist/language/en.json +0 -0
  110. /package/framework/{v0.3.9 → v0.3.10}/core/locales/dist/language/fr.json +0 -0
  111. /package/framework/{v0.3.9 → v0.3.10}/core/locales/dist/region/en.json +0 -0
  112. /package/framework/{v0.3.9 → v0.3.10}/core/locales/dist/region/fr.json +0 -0
  113. /package/framework/{v0.3.9 → v0.3.10}/core/locales/index.js +0 -0
  114. /package/framework/{v0.3.9 → v0.3.10}/core/mime.types +0 -0
  115. /package/framework/{v0.3.9 → v0.3.10}/core/model/entity.js +0 -0
  116. /package/framework/{v0.3.9 → v0.3.10}/core/model/index.js +0 -0
  117. /package/framework/{v0.3.9 → v0.3.10}/core/model/template/entityFactory.js +0 -0
  118. /package/framework/{v0.3.9 → v0.3.10}/core/model/template/index.js +0 -0
  119. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/README.md +0 -0
  120. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/index.js +0 -0
  121. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/csrf/README.md +0 -0
  122. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/csrf/package.json +0 -0
  123. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/csrf/src/main.js +0 -0
  124. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/file/README.md +0 -0
  125. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/file/build.json +0 -0
  126. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/file/package.json +0 -0
  127. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/intl/README.md +0 -0
  128. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/intl/build.json +0 -0
  129. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/intl/package.json +0 -0
  130. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/intl/src/main.js +0 -0
  131. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/session/README.md +0 -0
  132. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/session/package.json +0 -0
  133. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/storage/README.md +0 -0
  134. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/storage/build.json +0 -0
  135. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/storage/package.json +0 -0
  136. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/storage/src/main.js +0 -0
  137. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/validator/README.md +0 -0
  138. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/validator/build.json +0 -0
  139. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/validator/package.json +0 -0
  140. /package/framework/{v0.3.9 → v0.3.10}/core/plugins/lib/validator/src/form-validator.js +0 -0
  141. /package/framework/{v0.3.9 → v0.3.10}/core/router.js +0 -0
  142. /package/framework/{v0.3.9 → v0.3.10}/core/server.express.js +0 -0
  143. /package/framework/{v0.3.9 → v0.3.10}/core/status.codes +0 -0
  144. /package/framework/{v0.3.9 → v0.3.10}/core/template/_gitignore +0 -0
  145. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/app.json +0 -0
  146. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/connectors.json +0 -0
  147. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/routing.json +0 -0
  148. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/settings.json +0 -0
  149. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
  150. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/templates.json +0 -0
  151. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/config/watchers.json +0 -0
  152. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
  153. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
  154. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
  155. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle/index.js +0 -0
  156. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
  157. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/css/default.css +0 -0
  158. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/css/home.css +0 -0
  159. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
  160. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
  161. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
  162. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_public/readme.md +0 -0
  163. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
  164. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
  165. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
  166. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
  167. /package/framework/{v0.3.9 → v0.3.10}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
  168. /package/framework/{v0.3.9 → v0.3.10}/core/template/command/gina.bat.tpl +0 -0
  169. /package/framework/{v0.3.9 → v0.3.10}/core/template/command/gina.tpl +0 -0
  170. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/env.json +0 -0
  171. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/manifest.json +0 -0
  172. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/package.json +0 -0
  173. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/settings.json +0 -0
  174. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/statics.json +0 -0
  175. /package/framework/{v0.3.9 → v0.3.10}/core/template/conf/templates.json +0 -0
  176. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/client/json/401.json +0 -0
  177. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/client/json/403.json +0 -0
  178. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/client/json/404.json +0 -0
  179. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/server/html/50x.html +0 -0
  180. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/server/json/500.json +0 -0
  181. /package/framework/{v0.3.9 → v0.3.10}/core/template/error/server/json/503.json +0 -0
  182. /package/framework/{v0.3.9 → v0.3.10}/core/template/extensions/logger/config.json +0 -0
  183. /package/framework/{v0.3.9 → v0.3.10}/helpers/console.js +0 -0
  184. /package/framework/{v0.3.9 → v0.3.10}/helpers/context.js +0 -0
  185. /package/framework/{v0.3.9 → v0.3.10}/helpers/data/LICENSE +0 -0
  186. /package/framework/{v0.3.9 → v0.3.10}/helpers/data/README.md +0 -0
  187. /package/framework/{v0.3.9 → v0.3.10}/helpers/data/package.json +0 -0
  188. /package/framework/{v0.3.9 → v0.3.10}/helpers/data/src/main.js +0 -0
  189. /package/framework/{v0.3.9 → v0.3.10}/helpers/dateFormat.js +0 -0
  190. /package/framework/{v0.3.9 → v0.3.10}/helpers/index.js +0 -0
  191. /package/framework/{v0.3.9 → v0.3.10}/helpers/json/LICENSE +0 -0
  192. /package/framework/{v0.3.9 → v0.3.10}/helpers/json/README.md +0 -0
  193. /package/framework/{v0.3.9 → v0.3.10}/helpers/json/package.json +0 -0
  194. /package/framework/{v0.3.9 → v0.3.10}/helpers/json/src/main.js +0 -0
  195. /package/framework/{v0.3.9 → v0.3.10}/helpers/path.js +0 -0
  196. /package/framework/{v0.3.9 → v0.3.10}/helpers/plugins/README.md +0 -0
  197. /package/framework/{v0.3.9 → v0.3.10}/helpers/plugins/package.json +0 -0
  198. /package/framework/{v0.3.9 → v0.3.10}/helpers/plugins/src/api-error.js +0 -0
  199. /package/framework/{v0.3.9 → v0.3.10}/helpers/plugins/src/main.js +0 -0
  200. /package/framework/{v0.3.9 → v0.3.10}/helpers/prototypes.js +0 -0
  201. /package/framework/{v0.3.9 → v0.3.10}/helpers/task.js +0 -0
  202. /package/framework/{v0.3.9 → v0.3.10}/helpers/text.js +0 -0
  203. /package/framework/{v0.3.9 → v0.3.10}/lib/archiver/README.md +0 -0
  204. /package/framework/{v0.3.9 → v0.3.10}/lib/archiver/build.json +0 -0
  205. /package/framework/{v0.3.9 → v0.3.10}/lib/archiver/package.json +0 -0
  206. /package/framework/{v0.3.9 → v0.3.10}/lib/archiver/src/dep/jszip.min.js +0 -0
  207. /package/framework/{v0.3.9 → v0.3.10}/lib/archiver/src/main.js +0 -0
  208. /package/framework/{v0.3.9 → v0.3.10}/lib/async/package.json +0 -0
  209. /package/framework/{v0.3.9 → v0.3.10}/lib/async/src/main.js +0 -0
  210. /package/framework/{v0.3.9 → v0.3.10}/lib/cache/README.md +0 -0
  211. /package/framework/{v0.3.9 → v0.3.10}/lib/cache/build.json +0 -0
  212. /package/framework/{v0.3.9 → v0.3.10}/lib/cache/package.json +0 -0
  213. /package/framework/{v0.3.9 → v0.3.10}/lib/cache/src/main.js +0 -0
  214. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/aliases.json +0 -0
  215. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/add.js +0 -0
  216. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/arguments.json +0 -0
  217. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/build.js +0 -0
  218. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/copy.js +0 -0
  219. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/cp.js +0 -0
  220. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/help.js +0 -0
  221. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/help.txt +0 -0
  222. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/list.js +0 -0
  223. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/mcp-start.js +0 -0
  224. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/mcp.js +0 -0
  225. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/oas.js +0 -0
  226. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/openapi.js +0 -0
  227. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/remove.js +0 -0
  228. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/rename.js +0 -0
  229. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/restart.js +0 -0
  230. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/rm.js +0 -0
  231. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/start.js +0 -0
  232. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/status.js +0 -0
  233. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/bundle/stop.js +0 -0
  234. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/cache/stats.js +0 -0
  235. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/add.js +0 -0
  236. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/arguments.json +0 -0
  237. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/help.js +0 -0
  238. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/help.txt +0 -0
  239. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/list.js +0 -0
  240. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/remove.js +0 -0
  241. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/connector/rm.js +0 -0
  242. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/add.js +0 -0
  243. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/get.js +0 -0
  244. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/help.js +0 -0
  245. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/help.txt +0 -0
  246. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/link-dev.js +0 -0
  247. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/list.js +0 -0
  248. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/remove.js +0 -0
  249. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/rm.js +0 -0
  250. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/set.js +0 -0
  251. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/unset.js +0 -0
  252. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/env/use.js +0 -0
  253. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/arguments.json +0 -0
  254. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/build.js +0 -0
  255. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/dot.js +0 -0
  256. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/get.js +0 -0
  257. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/help.js +0 -0
  258. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/help.txt +0 -0
  259. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/init.js +0 -0
  260. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/link-node-modules.js +0 -0
  261. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/link.js +0 -0
  262. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/msg.json +0 -0
  263. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/open.js +0 -0
  264. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/restart.js +0 -0
  265. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/set.js +0 -0
  266. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/start.js +0 -0
  267. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/status.js +0 -0
  268. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/stop.js +0 -0
  269. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/tail.js +0 -0
  270. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/update.js +0 -0
  271. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/framework/version.js +0 -0
  272. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/gina-dev.1.md +0 -0
  273. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/gina-framework.1.md +0 -0
  274. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/gina.1.md +0 -0
  275. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/helper.js +0 -0
  276. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/index.js +0 -0
  277. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/inspector/help.js +0 -0
  278. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/inspector/open.js +0 -0
  279. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/minion/help.js +0 -0
  280. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/minion/help.txt +0 -0
  281. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/msg.json +0 -0
  282. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/help.js +0 -0
  283. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/help.txt +0 -0
  284. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/inc/scan.js +0 -0
  285. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/list.js +0 -0
  286. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/reset.js +0 -0
  287. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/port/set.js +0 -0
  288. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/add.js +0 -0
  289. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/arguments.json +0 -0
  290. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/build.js +0 -0
  291. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/help.js +0 -0
  292. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/help.txt +0 -0
  293. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/import.js +0 -0
  294. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/list.js +0 -0
  295. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/move.js +0 -0
  296. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/remove.js +0 -0
  297. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/rename.js +0 -0
  298. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/restart.js +0 -0
  299. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/rm.js +0 -0
  300. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/start.js +0 -0
  301. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/status.js +0 -0
  302. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/project/stop.js +0 -0
  303. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/protocol/help.js +0 -0
  304. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/protocol/help.txt +0 -0
  305. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/protocol/list.js +0 -0
  306. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/protocol/set.js +0 -0
  307. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/add.js +0 -0
  308. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/help.js +0 -0
  309. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/help.txt +0 -0
  310. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/link-local.js +0 -0
  311. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/link-production.js +0 -0
  312. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/list.js +0 -0
  313. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/remove.js +0 -0
  314. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/rm.js +0 -0
  315. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/scope/use.js +0 -0
  316. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/service/help.js +0 -0
  317. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/service/help.txt +0 -0
  318. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/service/list.js +0 -0
  319. /package/framework/{v0.3.9 → v0.3.10}/lib/cmd/view/add.js +0 -0
  320. /package/framework/{v0.3.9 → v0.3.10}/lib/collection/README.md +0 -0
  321. /package/framework/{v0.3.9 → v0.3.10}/lib/collection/build.json +0 -0
  322. /package/framework/{v0.3.9 → v0.3.10}/lib/collection/package.json +0 -0
  323. /package/framework/{v0.3.9 → v0.3.10}/lib/config.js +0 -0
  324. /package/framework/{v0.3.9 → v0.3.10}/lib/connector-registry/package.json +0 -0
  325. /package/framework/{v0.3.9 → v0.3.10}/lib/connector-registry/src/main.js +0 -0
  326. /package/framework/{v0.3.9 → v0.3.10}/lib/cron/README.md +0 -0
  327. /package/framework/{v0.3.9 → v0.3.10}/lib/cron/package.json +0 -0
  328. /package/framework/{v0.3.9 → v0.3.10}/lib/cron/src/main.js +0 -0
  329. /package/framework/{v0.3.9 → v0.3.10}/lib/domain/LICENSE +0 -0
  330. /package/framework/{v0.3.9 → v0.3.10}/lib/domain/README.md +0 -0
  331. /package/framework/{v0.3.9 → v0.3.10}/lib/domain/package.json +0 -0
  332. /package/framework/{v0.3.9 → v0.3.10}/lib/domain/src/main.js +0 -0
  333. /package/framework/{v0.3.9 → v0.3.10}/lib/generator/index.js +0 -0
  334. /package/framework/{v0.3.9 → v0.3.10}/lib/index.js +0 -0
  335. /package/framework/{v0.3.9 → v0.3.10}/lib/inherits/LICENSE +0 -0
  336. /package/framework/{v0.3.9 → v0.3.10}/lib/inherits/README.md +0 -0
  337. /package/framework/{v0.3.9 → v0.3.10}/lib/inherits/package.json +0 -0
  338. /package/framework/{v0.3.9 → v0.3.10}/lib/inherits/src/main.js +0 -0
  339. /package/framework/{v0.3.9 → v0.3.10}/lib/inspector-redact/package.json +0 -0
  340. /package/framework/{v0.3.9 → v0.3.10}/lib/inspector-redact/src/main.js +0 -0
  341. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/README.md +0 -0
  342. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/package.json +0 -0
  343. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/default/index.js +0 -0
  344. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/file/index.js +0 -0
  345. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
  346. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
  347. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/mq/index.js +0 -0
  348. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/mq/listener.js +0 -0
  349. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/containers/mq/speaker.js +0 -0
  350. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/helper.js +0 -0
  351. /package/framework/{v0.3.9 → v0.3.10}/lib/logger/src/main.js +0 -0
  352. /package/framework/{v0.3.9 → v0.3.10}/lib/math/index.js +0 -0
  353. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-dispatch/package.json +0 -0
  354. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-dispatch/src/main.js +0 -0
  355. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-http/package.json +0 -0
  356. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-http/src/main.js +0 -0
  357. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-server/package.json +0 -0
  358. /package/framework/{v0.3.9 → v0.3.10}/lib/mcp-server/src/main.js +0 -0
  359. /package/framework/{v0.3.9 → v0.3.10}/lib/merge/README.md +0 -0
  360. /package/framework/{v0.3.9 → v0.3.10}/lib/merge/package.json +0 -0
  361. /package/framework/{v0.3.9 → v0.3.10}/lib/merge/src/main.js +0 -0
  362. /package/framework/{v0.3.9 → v0.3.10}/lib/model.js +0 -0
  363. /package/framework/{v0.3.9 → v0.3.10}/lib/nunjucks-filters/README.md +0 -0
  364. /package/framework/{v0.3.9 → v0.3.10}/lib/nunjucks-filters/package.json +0 -0
  365. /package/framework/{v0.3.9 → v0.3.10}/lib/nunjucks-filters/src/main.js +0 -0
  366. /package/framework/{v0.3.9 → v0.3.10}/lib/nunjucks-resolver/package.json +0 -0
  367. /package/framework/{v0.3.9 → v0.3.10}/lib/nunjucks-resolver/src/main.js +0 -0
  368. /package/framework/{v0.3.9 → v0.3.10}/lib/proc.js +0 -0
  369. /package/framework/{v0.3.9 → v0.3.10}/lib/routing/README.md +0 -0
  370. /package/framework/{v0.3.9 → v0.3.10}/lib/routing/build.json +0 -0
  371. /package/framework/{v0.3.9 → v0.3.10}/lib/routing/package.json +0 -0
  372. /package/framework/{v0.3.9 → v0.3.10}/lib/routing/src/main.js +0 -0
  373. /package/framework/{v0.3.9 → v0.3.10}/lib/routing/src/radix.js +0 -0
  374. /package/framework/{v0.3.9 → v0.3.10}/lib/routing-introspect/package.json +0 -0
  375. /package/framework/{v0.3.9 → v0.3.10}/lib/routing-introspect/src/main.js +0 -0
  376. /package/framework/{v0.3.9 → v0.3.10}/lib/session-store.js +0 -0
  377. /package/framework/{v0.3.9 → v0.3.10}/lib/shell.js +0 -0
  378. /package/framework/{v0.3.9 → v0.3.10}/lib/state.js +0 -0
  379. /package/framework/{v0.3.9 → v0.3.10}/lib/swig-filters/README.md +0 -0
  380. /package/framework/{v0.3.9 → v0.3.10}/lib/swig-filters/package.json +0 -0
  381. /package/framework/{v0.3.9 → v0.3.10}/lib/swig-filters/src/main.js +0 -0
  382. /package/framework/{v0.3.9 → v0.3.10}/lib/swig-resolver/package.json +0 -0
  383. /package/framework/{v0.3.9 → v0.3.10}/lib/swig-resolver/src/main.js +0 -0
  384. /package/framework/{v0.3.9 → v0.3.10}/lib/url/README.md +0 -0
  385. /package/framework/{v0.3.9 → v0.3.10}/lib/url/index.js +0 -0
  386. /package/framework/{v0.3.9 → v0.3.10}/lib/url/routing.json +0 -0
  387. /package/framework/{v0.3.9 → v0.3.10}/lib/uuid/package.json +0 -0
  388. /package/framework/{v0.3.9 → v0.3.10}/lib/uuid/src/main.js +0 -0
  389. /package/framework/{v0.3.9 → v0.3.10}/lib/validator.js +0 -0
  390. /package/framework/{v0.3.9 → v0.3.10}/lib/watcher/package.json +0 -0
  391. /package/framework/{v0.3.9 → v0.3.10}/lib/watcher/src/main.js +0 -0
@@ -939,6 +939,22 @@ function ServerEngineClass(options) {
939
939
  process.gina.PROXY_HOST = request.headers['x-forwarded-host'];
940
940
  // console.debug('[PROXY_HOST][X-FORWARDED-PROTO] override request.headers["x-forwarded-host"] -> ' + request.headers['x-forwarded-host']);
941
941
  }
942
+ // Path-prefix awareness for upstreams mounted on a sub-path by the
943
+ // reverse proxy. Standard header used by Spring Boot, Traefik,
944
+ // FastAPI, etc. Normalised to leading slash + no trailing slash so
945
+ // downstream concatenation with the bundle's internal webroot is
946
+ // stable (e.g. "/admin" + "/" → "/admin/"). Empty or "/" values
947
+ // are dropped so back-compat is preserved.
948
+ if (request.headers['x-forwarded-prefix']) {
949
+ var _xfp = String(request.headers['x-forwarded-prefix']).trim();
950
+ _xfp = _xfp.replace(/\/+$/, '');
951
+ if (_xfp.length > 0 && _xfp.charAt(0) !== '/') {
952
+ _xfp = '/' + _xfp;
953
+ }
954
+ if (_xfp.length > 0) {
955
+ process.gina.PROXY_PREFIX = _xfp;
956
+ }
957
+ }
942
958
  // Forcing context - also available for workers
943
959
  setContext('isProxyHost', true);
944
960
  }
@@ -1063,7 +1063,7 @@ function Server(options) {
1063
1063
  // .replace(/((src|href|srcset)\=\"|(src|href|srcset)\=\')/g, '')
1064
1064
  // .replace(/\"/g, '')
1065
1065
  // ;
1066
- // FRAMEWORK PATCH (freelancer/v3): drop the `^` anchor so the
1066
+ // FRAMEWORK PATCH: drop the `^` anchor so the
1067
1067
  // decorative-quote strip is also skipped when `{{ }}` is embedded
1068
1068
  // mid-string (e.g. `css/main.css?cache={{ ''|formatDate('HH:MM:ss') }}`).
1069
1069
  // Without this, inner Swig string-literal quotes get stripped, the
@@ -2763,7 +2763,7 @@ function Server(options) {
2763
2763
  && self.instance._expressMiddlewares.length > 0
2764
2764
  ) {
2765
2765
 
2766
- // FRAMEWORK PATCH (freelancer/v3): Bug I — per-request dispatcher
2766
+ // FRAMEWORK PATCH: Bug I — per-request dispatcher
2767
2767
  var nextMiddleware = createNextMiddleware();
2768
2768
  nextMiddleware._index = 0;
2769
2769
  nextMiddleware._count = self.instance._expressMiddlewares.length-1;
@@ -3637,7 +3637,7 @@ function Server(options) {
3637
3637
  * @private
3638
3638
  * @param {Error|boolean} err - Error from the previous middleware, or false
3639
3639
  */
3640
- // FRAMEWORK PATCH (freelancer/v3): Bug I — wrap nextMiddleware in a
3640
+ // FRAMEWORK PATCH: Bug I — wrap nextMiddleware in a
3641
3641
  // per-request factory. The original function held dispatch state on its
3642
3642
  // own properties (._index, ._request, ._response, ._next, ._nextAction).
3643
3643
  // Under concurrent requests, request B's setup at the entry point
@@ -4114,7 +4114,7 @@ function Server(options) {
4114
4114
 
4115
4115
  if (matched) {
4116
4116
  if ( /^isaac/.test(self.engine) && self.instance._expressMiddlewares.length > 0) {
4117
- // FRAMEWORK PATCH (freelancer/v3): Bug I — per-request dispatcher
4117
+ // FRAMEWORK PATCH: Bug I — per-request dispatcher
4118
4118
  var nextMiddleware = createNextMiddleware();
4119
4119
  nextMiddleware._index = 0;
4120
4120
  nextMiddleware._count = self.instance._expressMiddlewares.length-1;
@@ -30,7 +30,7 @@ var CmdHelper = require('./../helper');
30
30
  * The framework config loader (`core/config.js`) is NOT modified by this
31
31
  * session. There is no runtime auto-migration hook — this subcommand is
32
32
  * explicit, opt-in, and CI-friendly only. See
33
- * `.claude/todo/cn10-connector-cli-plan.md` § "Recommendation (narrower C)"
33
+ * the internal plan doc § "Recommendation (narrower C)"
34
34
  * for the rationale: no real old-shape → new-shape delta exists today, so
35
35
  * touching `Config.load()` on the boot path would be premature. Revisit
36
36
  * when a concrete migration (e.g. #CN8 Couchbase SDK v2 removal at
@@ -35,8 +35,8 @@
35
35
 
36
36
  gina inspector:open Open for default bundle on port 3100
37
37
  gina inspector:open demo @myproject Open for bundle "demo" (uses settings.inspector.url if set)
38
- gina inspector:open coreapi @freelancer Bundle on a project other than @gina
39
- gina inspector:open https://v3-local.example.com/api/
38
+ gina inspector:open coreapi @myotherproject Bundle on a project other than @gina
39
+ gina inspector:open https://local.example.com/api/
40
40
  URL target — Inspector SPA opens with that origin
41
41
  gina inspector:open --port=3200 Open for a specific port (embedded)
42
42
  gina inspector:open --browser=chrome Force Chrome (app mode)
@@ -158,7 +158,12 @@ function Collection(content, options) {
158
158
  if ( typeof(content[entry]._uuid) != 'undefined' ) {
159
159
  content[entry]._hasItsOwnUuid = true;
160
160
  } else {
161
- content[entry]._uuid = uuid();
161
+ // 16-char base-62 (~4.77e28 space) — at the default 4-char (~14.78M) the
162
+ // birthday-paradox collision rate at N=917 is ~2.84%, and `notIn()`'s
163
+ // _uuid-keyed splice removes the wrong record on collision. 16 keeps the
164
+ // collision rate below 1e-13 up to N=100M, future-proof for any practical
165
+ // collection size.
166
+ content[entry]._uuid = uuid(16);
162
167
  }
163
168
 
164
169
  // To avoid duplicate entries
@@ -941,8 +946,8 @@ function Collection(content, options) {
941
946
 
942
947
  var tmpContent = Array.isArray(this) ? this : content;
943
948
 
944
- // Indexing;
945
- set._uuid = uuid();
949
+ // Indexing; 16-char to match the constructor's collision-safe size.
950
+ set._uuid = uuid(16);
946
951
  tmpContent.push(set);
947
952
 
948
953
  result = tmpContent;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gina-framework",
3
- "version": "0.3.9-alpha.2",
3
+ "version": "0.3.10-alpha.2",
4
4
  "dependencies": {
5
5
  "@rhinostone/swig": "^1.6.0",
6
6
  "psl": "^1.15.0"
package/gna.js CHANGED
@@ -15,14 +15,14 @@
15
15
  'use strict';
16
16
 
17
17
  // Framework core — the main gna module (lifecycle hooks, lib, etc.)
18
- var _gna = require('./framework/v0.3.9/core/gna');
18
+ var _gna = require('./framework/v0.3.10/core/gna');
19
19
 
20
20
  // SuperController and EntitySuper — loaded from their source modules
21
- var SuperController = require('./framework/v0.3.9/core/controller');
22
- var EntitySuper = require('./framework/v0.3.9/core/model/entity');
21
+ var SuperController = require('./framework/v0.3.10/core/controller');
22
+ var EntitySuper = require('./framework/v0.3.10/core/model/entity');
23
23
 
24
24
  // uuid — from the lib registry
25
- var uuid = require('./framework/v0.3.9/lib/uuid');
25
+ var uuid = require('./framework/v0.3.10/lib/uuid');
26
26
 
27
27
  module.exports = {
28
28
 
package/llms.txt CHANGED
@@ -813,13 +813,13 @@ Dev-mode query instrumentation captures every database query tied to the current
813
813
 
814
814
  64. **MCP Streamable HTTP default security posture — loopback bind, Origin allowlist, bearer optional, OAuth out of scope** — when `bundle:mcp-start --transport=http` lands, the canonical security model is (1) bind `127.0.0.1` by default via the framework's existing `host_v4` convention (same chain as the MQ listener and CLI daemon socket — `GINA_HOST_V4` env from `~/.gina/<shortVersion>/settings.json > host_v4`); (2) `Origin` allowlist with a built-in loopback set (`http(s)://localhost`, `127.0.0.1`, `[::1]` on any port) + DNS-rebinding-mitigation 403 on mismatch; (3) static `--auth-token` bearer optional (constant-time via `crypto.timingSafeEqual` with length-mismatch short-circuit — `timingSafeEqual` throws on unequal-length buffers); (4) **OAuth 2.1 is deliberately out of scope** — the community pattern is a reverse proxy (oauth2-proxy, Traefik ForwardAuth, nginx `auth_request`) that handles the OAuth dance and forwards a static-bearer or no-auth request to gina. Traps: (a) WHATWG `URL` returns IPv6 hostnames bracketed (`[::1]`, not `::1`) — loopback check must accept both forms or `http://[::1]:8080` silently 403s; (b) `res.writeHead(status, {...})` passes headers via object — use `setHeader` if you want setHeader + writeHead to merge, or merge manually in one writeHead call; (c) 403 on disallowed Origin must NOT echo CORS (would defeat the check) but 401 on missing/invalid bearer MUST echo CORS (the client is legitimate, it just needs to auth — without ACAO the browser hides the error body); (d) preflight bypasses bearer (browsers cannot carry `Authorization` on an OPTIONS preflight); (e) `http.Server.close()` waits for keep-alive timeout (Node default ~5s) even when no request is in flight — set `keepAliveTimeout = 1` + call `closeIdleConnections()` on stop so SIGTERM shutdowns drain promptly. Library: `lib/mcp-http/`. CLI surface: `--transport=stdio|http`, `--http-host`, `--http-port`, `--max-in-flight`, `--auth-token`, `--cors-origin`. Each flag has a `mcp.json > server > <field>` manifest fallback. Precedent: `#AI8 Phase 2b` (sessions 1–4, commits `79ed1fec5`, `13f767cff`, `1c6d39de3`, `<S4>`, 2026-04-22).
815
815
 
816
- 65. **`lib/swig-resolver` — process-cached swig with runtime opt-in** — `core/server.js:33` and `core/controller/controller.js:56` no longer call `require('@rhinostone/swig')` at module load. Instead they go through `lib.swigResolver`, which resolves and caches the module on `process.gina._swig` during bundle init (`initSwigEngine()` in `server.js` calls `swigResolver.load(self.executionPath, conf.content.settings.swig || {})` before the first swig call). Two bundle settings drive the lookup: `swig.useProject: true` (default `false`) enables the project-side search in `<executionPath>/node_modules/<pkg>/`, and `swig.package` (enum `@rhinostone/swig` | `@rhinostone/swig-twig`, default `@rhinostone/swig`) picks the npm package name. Three safety gates reject the project pin and fall back to the framework copy with a `[swig-resolver]` warning: (a) package-name allowlist — the abandoned upstream `swig` name is never resolvable, preventing CVE-2023-25345 re-entry; (b) same-major rule — project major must equal the floor's major; (c) min-version floor — within the same major, project version must be ≥ `DEFAULT_MIN` in `lib/swig-resolver/src/main.js` (`1.6.0` today; bump in the same commit that depends on a new fork API — see the `CLAUDE.md` "Swig version sync" rule). Dev-mode survival: `process.gina._swig` persists through `refreshCoreDependencies()` evictions, so the per-request re-require of `controller.js` picks up the same instance without re-running the resolver. Standalone mode (multiple bundles in one process) is first-wins — a second bundle requesting a different package is warned and keeps the loaded copy; split to one bundle per process for per-bundle isolation. Library exports: `resolve` (pure decision), `load` (resolve + require + cache), `get` (cached getter with framework fallback), `getDecision`, `reset` (test helper only). `require.resolve` parses the package's manifest to find `main`, so a malformed `package.json` throws `ERR_INVALID_PACKAGE_CONFIG` which looks identical to `MODULE_NOT_FOUND` — the resolver probes `<projectPath>/node_modules/<pkg>/package.json` directly first, so the emitted warning code is accurate (`malformed-package-json` vs `not-installed`). Dev-mode hot-swap: when `NODE_ENV_IS_DEV=true`, every `get()` call `fs.statSync`-probes the project's `package.json`; if `mtimeMs` drifted since the last `load()` — e.g. after `npm install @rhinostone/swig@<newer>` — the cached module is evicted from `require.cache`, `process.gina._swig` is cleared, and `load()` re-runs with the original options. Production bundles skip the probe entirely. Test-author trap: `Date.now()` has millisecond resolution, so two `fs.utimesSync(path, Date.now()+X, Date.now()+X)` calls within the same wall-clock ms produce identical mtimes — fixture helpers must use a monotonic counter (`Math.max(prev, Date.now(), lastMtime) + 1000`) to guarantee strict progression. On macOS `os.tmpdir()` returns `/var/folders/...` while `require.cache` keys are realpath-normalised to `/private/var/folders/...` — `fs.realpathSync` the fixture root inside `before()` or the cache eviction silently no-ops. Precedent: `730604ecd` (S1 primitive), `08bf03cd1` (S2 wiring), `f568192e8` (S2b dev hot-swap), 2026-04-22.
816
+ 65. **`lib/swig-resolver` — process-cached swig with runtime opt-in** — `core/server.js:33` and `core/controller/controller.js:56` no longer call `require('@rhinostone/swig')` at module load. Instead they go through `lib.swigResolver`, which resolves and caches the module on `process.gina._swig` during bundle init (`initSwigEngine()` in `server.js` calls `swigResolver.load(self.executionPath, conf.content.settings.swig || {})` before the first swig call). Two bundle settings drive the lookup: `swig.useProject: true` (default `false`) enables the project-side search in `<executionPath>/node_modules/<pkg>/`, and `swig.package` (enum `@rhinostone/swig` | `@rhinostone/swig-twig`, default `@rhinostone/swig`) picks the npm package name. Three safety gates reject the project pin and fall back to the framework copy with a `[swig-resolver]` warning: (a) package-name allowlist — the abandoned upstream `swig` name is never resolvable, preventing CVE-2023-25345 re-entry; (b) same-major rule — project major must equal the floor's major; (c) min-version floor — within the same major, project version must be ≥ `DEFAULT_MIN` in `lib/swig-resolver/src/main.js` (`1.6.0` today; bump in the same commit that depends on a new fork API — see the global "Swig version sync" rule). Dev-mode survival: `process.gina._swig` persists through `refreshCoreDependencies()` evictions, so the per-request re-require of `controller.js` picks up the same instance without re-running the resolver. Standalone mode (multiple bundles in one process) is first-wins — a second bundle requesting a different package is warned and keeps the loaded copy; split to one bundle per process for per-bundle isolation. Library exports: `resolve` (pure decision), `load` (resolve + require + cache), `get` (cached getter with framework fallback), `getDecision`, `reset` (test helper only). `require.resolve` parses the package's manifest to find `main`, so a malformed `package.json` throws `ERR_INVALID_PACKAGE_CONFIG` which looks identical to `MODULE_NOT_FOUND` — the resolver probes `<projectPath>/node_modules/<pkg>/package.json` directly first, so the emitted warning code is accurate (`malformed-package-json` vs `not-installed`). Dev-mode hot-swap: when `NODE_ENV_IS_DEV=true`, every `get()` call `fs.statSync`-probes the project's `package.json`; if `mtimeMs` drifted since the last `load()` — e.g. after `npm install @rhinostone/swig@<newer>` — the cached module is evicted from `require.cache`, `process.gina._swig` is cleared, and `load()` re-runs with the original options. Production bundles skip the probe entirely. Test-author trap: `Date.now()` has millisecond resolution, so two `fs.utimesSync(path, Date.now()+X, Date.now()+X)` calls within the same wall-clock ms produce identical mtimes — fixture helpers must use a monotonic counter (`Math.max(prev, Date.now(), lastMtime) + 1000`) to guarantee strict progression. On macOS `os.tmpdir()` returns `/var/folders/...` while `require.cache` keys are realpath-normalised to `/private/var/folders/...` — `fs.realpathSync` the fixture root inside `before()` or the cache eviction silently no-ops. Precedent: `730604ecd` (S1 primitive), `08bf03cd1` (S2 wiring), `f568192e8` (S2b dev hot-swap), 2026-04-22.
817
817
 
818
818
  66. **changie body strings — single-quote by default and escape `'` as `''`** — six `.changes/unreleased/*.yaml` files written across four separate sessions in April 2026 broke YAML parsing in two distinct ways, one loud and one silent. Loud: a quoted body without a closing `'` before the newline fails with `unexpected end of stream`; an unescaped literal `'` inside a quoted body closes the scalar early and the rest of the file parses as garbage (`did not find expected key`, `mapping values are not allowed`). Silent and worse: an unquoted body containing ` #` (space + hash) — YAML treats that as a comment marker, so `(#M8 / #AI3)` was stored as just `(#M8 /` and the rest of the sentence disappeared from the rendered changelog entirely; likewise an unquoted body with `:` at any depth breaks as a mapping. Cleaned up in commit `ca64f0735` (2026-04-22) — `Added-20260417-200100.yaml`, `Added-20260417-203000.yaml`, `Fixed-20260416-183051.yaml`, `Added-20260422-022257.yaml`, `Added-20260422-023136.yaml`, `Added-20260422-024132.yaml`. Author rule: every new changie entry uses `body: '...'` (single-quoted) even when the text looks "clean"; every literal `'` inside becomes `''`; never leave a body unquoted. Verification is now automatic: `.githooks/pre-commit` runs `script/check_changie_entries.js` against every staged `.changes/unreleased/*.yaml` file and refuses the commit when any body is unquoted, uses an unescaped literal `''` inside a single-quoted scalar, or fails to terminate before the `time:` key (commit `acbf91dd0`, 2026-04-22). The script is also callable standalone (`node script/check_changie_entries.js .changes/unreleased/*.yaml`) for CI use or on-demand audits. Pre-existing unquoted entries on disk are grandfathered until someone edits them — the hook only runs on staged AM files. The on-demand `changie batch --dry-run patch` still works for a visual sanity check of the rendered markdown.
819
819
 
820
820
  67. **`lib/nunjucks-resolver` + `settings.render.engine` — opt-in nunjucks without a framework dep** — nunjucks is **never** declared in `framework/v*/package.json`; the resolver only looks in `<projectPath>/node_modules/nunjucks/`. Three-layer dispatch: (a) `schema/settings.json > render.engine` defaults to `"swig"`; set to `"nunjucks"` to opt in. (b) `server.js` `initNunjucksEngine(conf)` runs alongside `initSwigEngine` during bundle init when `hasViews()` is true — if `render.engine === 'nunjucks'`, it calls `nunjucksResolver.load(self.executionPath, settings.nunjucks)` which throws `NUNJUCKS_NOT_INSTALLED` when the project has not `npm install`ed the package (bundle startup fails loud, not mid-render). (c) `controller.js` `this.render()` reads `local.options.conf.content.settings.render.engine` and dispatches to either `controller.render-swig.js` (default) or `controller.render-nunjucks.js`; both delegates receive the same `deps` object (`self`, `local`, `getData`, `hasViews`, `headersSent`, etc.), `render-nunjucks.js` fetches the engine via `lib.nunjucksResolver.get()` so the `swig`/`SwigFilters` deps are harmless when unused. Environment caching: `new nunjucks.Environment(loader, ...)` is cached per template root on `process.gina._nunjucksEnvs` and invalidated via `process.gina._nunjucksEnvsOwner` whenever the underlying nunjucks module itself is hot-swapped (dev-mode mtime drift on the project''s `package.json`). Dev-mode also sets `FileSystemLoader({ noCache: true })` so `.njk` template edits take effect on the next request. **Deferred relative to render-swig.js** — documented inline so nothing is hidden: (1) ~~Inspector `__gdPayload`~~ **shipped 2026-04-22** (commit `7aef191ac`) — `<script>window.__ginaData</script>` + `<script>window.__ginaLogs</script>` injected before `</body>` in dev mode via `injectInspectorScripts`, redacted through `lib/inspector-redact`, stashed on `serverInstance._lastGinaData` (+ `_lastGinaDataUnredacted` on local scope), emitted via `process.emit('inspector#data')`, wrapped in try/catch so Inspector bugs never break the page render. Within the Inspector port, three sub-items remain deferred: `statusbar.html` include (swig-templated), `data.page.flow` pipeline from `local._timeline`, `data.page.queries` from `local._queryLog`. (2) ~~HTTP/2 `stream.respond()` direct path~~ **shipped 2026-04-22** (commit `6b11fc6dc`) — `sendHtmlResponse` implements the four-way branch from `class.controller.md §7b` (HEAD×stream, HEAD×HTTP1.1, body×stream, body×HTTP1.1) with the `stream.destroyed || stream.closed` disconnect guard, pipeline-header merge via `local.res.getHeaders()`, and `local.res.headersSent = true` flag. (3) ~~Error-page template routing~~ **shipped 2026-04-22** — when `controller.js renderCustomError()` injects `errOptions.file = <absolute path>` from `bundleConf.content.templates._common.errorFiles[code]`, `render-nunjucks.js` now detects `localOptions.isRenderingCustomError === true`, reads the file via `fs.readFileSync`, and renders it with `env.renderString(source, data)`. `env.renderString` bypasses the `FileSystemLoader` (which rejects absolute paths and cannot reach shared-path error templates outside the bundle root). **Never recurses via `self.throwError`** — every failure mode (missing file, read error, render error) falls back to a minimal inline HTML body served through `sendHtmlResponse`, because a `throwError` call would re-enter this same branch and could loop infinitely. Defensive `localOptions.isRenderingCustomError = false` reset after render mirrors `render-swig.js` lines 804 and 1434. +14 source-inspection tests in `test/lib/render-engine-dispatch.test.js` (78 total in the dispatch suite, 116 total across the nunjucks track, full suite 3326 → 3340 green). (4) ~~Early Hints 103 auto-send~~ **shipped 2026-04-23** (#NJ4) — feature-complete confirmation only, no code change needed. The `#EH1` firing point at `controller.js:1034-1044` is engine-agnostic (reads `local.options.template.h2Links` and calls `self.setEarlyHints(_hints)` BEFORE the delegate dispatch); the data-feed was closed by #NJ2 because `deps.setResources` is the same function object (defined once in `controller.js:782`) passed to both delegates, and `setResources` → `getNodeRes` writes to `local.options.template.h2Links` at `controller.js:901` (CSS) / `:930` (JS) on HTTP/2 non-dev requests regardless of which delegate invoked it. Negative invariants in `test/lib/render-engine-dispatch.test.js §05f` lock in the single-source rule (no `h2Links` reads or writes anywhere in `render-nunjucks.js` module code; `#EH1` marker present only in `controller.js`). +22 source-inspection + behavioural tests (160 total in the dispatch suite, 198 across the nunjucks track; full suite 3452 → 3474). **Closes the four-session ASAP nunjucks parity track** (#NJ1 filters / #NJ2 setResources & assets / #NJ3 static HTML cache / #NJ4 Early Hints) — nunjucks bundles now have full feature parity with swig. (5) ~~static HTML cache writes~~ **shipped 2026-04-23** (#NJ3) — `writeCache(local, self, bundle, opt, htmlContent)` ported verbatim from `render-swig.js:35-129` (same guards, `static:<bundle>:<url>` key shape, memory/fs dispatch, sliding-window, `invalidateOnEvents`). Helper takes `local`/`self` explicitly because render-nunjucks.js does not hoist those into module scope the way render-swig.js does. Module-level `cache = new lib.Cache()` re-pointed per-request via `cache.from(self.serverInstance._cached)`. Call site runs AFTER `injectAssets` + `injectInspectorScripts` and BEFORE `sendHtmlResponse`; wrapped in try/catch so a cache-write failure never breaks the render. Miss-path `Cache-Control: <visibility>, max-age=N` header set in the same block. READ path is engine-agnostic already — `server.isaac.js:1012-1067` reads from `static:<bundle>:<url>` regardless of which engine populated the entry, so no read-side port was needed. +33 source-inspection + behavioural tests in `test/lib/render-engine-dispatch.test.js` §05e (138 total in the dispatch suite, 176 across the nunjucks track; full suite 3419 → 3452). Third of the four-session ASAP nunjucks parity track. (6) ~~`setResources` asset injection / `<gina>` layout placeholders~~ **shipped 2026-04-23** (#NJ2). (7) ~~Gina `SwigFilters` equivalents~~ **shipped 2026-04-23** (#NJ1) — `lib/nunjucks-filters` factory mirrors the 7 public `lib/swig-filters`, registered per-request via `env.addFilter`. Negative invariants locked by source-inspection tests in `test/lib/render-engine-dispatch.test.js`: framework `package.json` must never declare nunjucks; `render-nunjucks.js` must never `require("nunjucks")` directly (always through the resolver); dispatch default must stay `"swig"`; the `isRenderingCustomError` branch must never call `self.throwError` (would loop). Nunjucks contrast vs swig-resolver: no framework fallback (different contract — `get()` throws instead of returning a framework copy), no version floor (any installed version accepted), same mtime hot-swap mechanism. Precedent: `63e7e569f` (N1 resolver primitive), `0980a00b7` (N2 dispatch + render-nunjucks.js MVP), `7aef191ac` (Inspector `__gdPayload` port), `6b11fc6dc` (HTTP/2 stream.respond four-way branch), error-page template routing (commit `07b1ea087`), `6d5ab382` (#NJ1 filters), `b2466398` (#NJ2 setResources/injectAssets), `680c2637` (#NJ3 static HTML cache), and this commit (#NJ4 Early Hints 103 — closes the four-session nunjucks parity track), 2026-04-22 / 2026-04-23.
821
821
 
822
- 68. **Local-tool path leaks have three surfaces — commit messages, changie YAML bodies, JSDoc comments in shipped source — and their cost scales with unpushed-commit backlog** — the CLAUDE.md HARD RULE on no-local-tool-paths-on-public-surfaces names the categories (filenames, directory names, feature-description mentions) but today's pre-flight scan before pushing 23 unpushed commits surfaced three distinct contamination surfaces that each need their own scanner. Notation: `<tool-dir>` = the ignored local-tool config tree named by the existing `.githooks/pre-commit` regex `(^|/)(CLAUDE\.md|\.claude[a-z]*)`; sub-paths below use `<tool-dir>/todo/<plan>.md`, `<tool-dir>/architecture/<file>.md`, etc. (1) **commit messages / bodies** — run `git log origin/<branch>..<branch> --format='%H%n%s%n%b%n==='` then pipe through the same regex the hook uses plus `anthropic|generated by|co-[aa]uthored-[bb]y|opus [0-9]`; today caught a `Plan: <tool-dir>/todo/<plan>.md` footer in three `#AI8 Phase 2b` commits (`79ed1fec5` / `13f767cff` / `1c6d39de3`). (2) **changie YAML bodies that ship as `CHANGELOG.md` content** — `.changes/unreleased/*.yaml` `body:` strings become the published changelog on npm + GitHub + the docs site. Today caught two entries (`Changed-20260422-141823.yaml`, `Changed-20260422-161419.yaml`) referencing `<tool-dir>/architecture/<file>.md §N` anchors. Scan with the leak-scan regex against `.changes/unreleased/*.yaml` before every `changie batch`. (3) **JSDoc comments in shipped source files** — `framework/v*/**/*.js` JSDoc headers travel inside the npm-packed tarball and are publicly visible via `npm view gina@<version>` + unpacked `node_modules/gina/`. Today caught a `sendHtmlResponse` JSDoc block in `controller.render-nunjucks.js` cross-referencing an internal architecture doc. Scan the full diff (`git diff origin/<branch>...<branch> -- | grep '^+' | grep -v '^+++'` piped through the leak-scan regex) not just the latest commit. **Neutral-replacement pattern**: `<tool-dir>/todo/<plan>.md` → "the internal plan doc"; `<tool-dir>/architecture/<file>.md §N` → "the internal architecture docs" (drop the `§N` anchor — it's meaningful only to a reader with access to the file). **Allowed exceptions** (do NOT rewrite): `.githooks/pre-commit`, `script/check_no_local_leak.js`, `.gitignore`, `.npmignore`, and the `CLAUDE.md` global rule itself — these ARE the leak-scan product functionality and must name the pattern they match. **Accumulation-tax observation**: skipping `git push` after each small-batch commit turns a one-commit remediation into a 5-of-23-commit rewrite pass at release time. The "Ship more in small batches" rule's "one commit per push when feasible" half is where the tax is paid — delay one week of pushes, pay a `git filter-repo --replace-text` + force-push + CI-wait round instead of a trivial `git push`. **Remediation commands** for an unreleased feature branch with mixed leaks: `git tag backup/<branch>-<date> <branch>` → write expressions file with `literal:<path>==><neutral>` lines → `git filter-repo --replace-text <file> --refs origin/<branch>..<branch> --force` (scoped to exclusive commits so origin's published history is untouched) → re-scan → `git push --force-with-lease origin <branch>`. Alternative for small commit counts with mixed message + content changes: cherry-pick-replay onto a temp branch from the origin baseline, `git commit --amend -m '<new msg>'` or edit + `git add <file>` + `git commit --amend --no-edit` for the problematic ones, then `git branch -f <branch> temp` and force-push. Precedent: this session's docs-repo PR `gina-io/docs#8` (3 commits rewritten via cherry-pick-replay for a shared-path filename + 2 msg mentions) and the deferred gina alpha release (5 commits queued for `git filter-repo --replace-text` per the internal release plan doc), 2026-04-22.
822
+ 68. **Local-tool path leaks have three surfaces — commit messages, changie YAML bodies, JSDoc comments in shipped source — and their cost scales with unpushed-commit backlog** — the global HARD RULE on no-local-tool-paths-on-public-surfaces names the categories (filenames, directory names, feature-description mentions) but today's pre-flight scan before pushing 23 unpushed commits surfaced three distinct contamination surfaces that each need their own scanner. Notation: `<tool-dir>` = the ignored local-tool config tree named by the existing `.githooks/pre-commit` local-tool path regex; sub-paths below use `<tool-dir>/todo/<plan>.md`, `<tool-dir>/architecture/<file>.md`, etc. (1) **commit messages / bodies** — run `git log origin/<branch>..<branch> --format='%H%n%s%n%b%n==='` then pipe through the same regex the hook uses plus the AI-attribution patterns; today caught a `Plan: <tool-dir>/todo/<plan>.md` footer in three `#AI8 Phase 2b` commits (`79ed1fec5` / `13f767cff` / `1c6d39de3`). (2) **changie YAML bodies that ship as `CHANGELOG.md` content** — `.changes/unreleased/*.yaml` `body:` strings become the published changelog on npm + GitHub + the docs site. Today caught two entries (`Changed-20260422-141823.yaml`, `Changed-20260422-161419.yaml`) referencing `<tool-dir>/architecture/<file>.md §N` anchors. Scan with the leak-scan regex against `.changes/unreleased/*.yaml` before every `changie batch`. (3) **JSDoc comments in shipped source files** — `framework/v*/**/*.js` JSDoc headers travel inside the npm-packed tarball and are publicly visible via `npm view gina@<version>` + unpacked `node_modules/gina/`. Today caught a `sendHtmlResponse` JSDoc block in `controller.render-nunjucks.js` cross-referencing an internal architecture doc. Scan the full diff (`git diff origin/<branch>...<branch> -- | grep '^+' | grep -v '^+++'` piped through the leak-scan regex) not just the latest commit. **Neutral-replacement pattern**: `<tool-dir>/todo/<plan>.md` → "the internal plan doc"; `<tool-dir>/architecture/<file>.md §N` → "the internal architecture docs" (drop the `§N` anchor — it's meaningful only to a reader with access to the file). **Allowed exceptions** (do NOT rewrite): `.githooks/pre-commit`, `script/check_no_local_leak.js`, `.gitignore`, `.npmignore`, and the global rule documentation itself — these ARE the leak-scan product functionality and must name the pattern they match. **Accumulation-tax observation**: skipping `git push` after each small-batch commit turns a one-commit remediation into a 5-of-23-commit rewrite pass at release time. The "Ship more in small batches" rule's "one commit per push when feasible" half is where the tax is paid — delay one week of pushes, pay a `git filter-repo --replace-text` + force-push + CI-wait round instead of a trivial `git push`. **Remediation commands** for an unreleased feature branch with mixed leaks: `git tag backup/<branch>-<date> <branch>` → write expressions file with `literal:<path>==><neutral>` lines → `git filter-repo --replace-text <file> --refs origin/<branch>..<branch> --force` (scoped to exclusive commits so origin's published history is untouched) → re-scan → `git push --force-with-lease origin <branch>`. Alternative for small commit counts with mixed message + content changes: cherry-pick-replay onto a temp branch from the origin baseline, `git commit --amend -m '<new msg>'` or edit + `git add <file>` + `git commit --amend --no-edit` for the problematic ones, then `git branch -f <branch> temp` and force-push. Precedent: this session's docs-repo PR `gina-io/docs#8` (3 commits rewritten via cherry-pick-replay for a shared-path filename + 2 msg mentions) and the deferred gina alpha release (5 commits queued for `git filter-repo --replace-text` per the internal release plan doc), 2026-04-22.
823
823
 
824
824
 
825
825
  69. **Release-session tooling traps — three gotchas surfaced during the `gina@0.3.7-alpha.2` publish pass** — each looks unrelated but all share the theme "the release gate is where routine assumptions break." (1) **`git filter-repo --replace-text` rewrites blob content only, NOT commit messages** — today's first rewrite pass cleaned the diff of 3 leaking strings across 24 commits but left the `Plan: <internal>.md` footer untouched in 3 commit message bodies. `--replace-text` and `--replace-message` are independent flags — a mixed-surface leak needs BOTH in the same invocation (or two sequential passes). Pattern: `git filter-repo --replace-text /tmp/content.txt --replace-message /tmp/messages.txt --refs origin/<branch>..<branch> --force`. The two files can share expressions (identical `literal:<leak>==><neutral>` lines) — they're scoped to different surfaces, not different strings. Re-scan after: `git log <range> --format='%H%n%s%n%b%n===' | grep -iE <leak-regex>` to verify messages are clean; `git diff origin/<branch>...<branch> | grep '^+' | grep <leak-regex>` to verify diffs are clean. (2) **GitHub branch protection on `master` rejects force-push even for admins when `enforce_admins: false`** — the `enforce_admins` flag's semantics are counter-intuitive: `false` means admins are NOT subject to the restrictions (they can bypass), `true` means they ARE subject. But "bypass" does not extend to `allow_force_pushes: false` — that flag is categorical for everyone including admins. Today's master scrub got `remote: error: GH006: Protected branch update failed` despite being repo admin. The fix: toggle `allow_force_pushes` via `gh api` before the push, then restore after. One-shot via full protection PUT: `gh api -X PUT repos/<org>/<repo>/branches/<branch>/protection --input <json>` with `allow_force_pushes: true` (the direct `.../protection/allow_force_pushes` endpoint returns 404 — it isn't a thing). Write the full JSON including `required_status_checks: null`, `restrictions: null`, the existing `required_pull_request_reviews` block, and all boolean flags — a partial PUT replaces missing keys with defaults and may silently relax other protections. Cycle: read current protection → copy into /tmp/<branch>-protection.json → flip `allow_force_pushes: true` → PUT → `git push --force-with-lease=<branch>:<old-sha> origin <branch>` → also `git push --force origin refs/tags/v*:refs/tags/v*` for any release tags that got rewritten → flip back to `false` → PUT again. Verify after both toggles: `gh api repos/<org>/<repo>/branches/<branch>/protection | jq .allow_force_pushes.enabled`. (3) **`framework/v*/package.json` is gitignored but read by tests — CI fails unless the workflow regenerates it inline** — the file drives swig/nunjucks-resolver source-inspection tests (e.g. `framework/v*/package.json does NOT declare nunjucks as a dep` reads `fs.readFileSync(path.join(FW, 'package.json'), 'utf8')`). Since `.gitignore` carries `framework/v*/package.json`, a fresh CI checkout never has it, and the tests fail with `ENOENT` or stale content. Local runs pass because the user previously ran `npm install` inside the framework dir, leaving the file on disk. Reusable rule for any gitignored-but-test-required file: add an explicit CI step that derives the file's content from a tracked source (root `package.json` version, a template, or the resolver's `DEFAULT_MIN`) and writes it before `npm install` inside that dir. Heredoc pattern in `.github/workflows/test.yml`:
@@ -847,17 +847,17 @@ YAML indent trap: the heredoc body and the `EOF` terminator must land at column
847
847
 
848
848
  71. **The nunjucks render path needs BOTH a pre-render `setResources()` call AND a post-render `injectAssets()` pass — the two cooperate via exact-substring user-placement detection** — when porting the swig path's asset cataloguing + `<gina>` layout placeholder system (#NJ2, 2026-04-23), the obvious design is "do it one way or the other". Both fail. Pre-render-only: `data.page.view.stylesheets` / `.scripts` are populated, but only templates that explicitly reference `{{ page.view.stylesheets | safe }}` / `{{ page.view.scripts | safe }}` get any assets — existing bundles that relied on swig's auto-injection would silently ship no stylesheets. Post-render-only: assets are injected everywhere, but templates that DO reference `{{ page.view.stylesheets | safe }}` end up with the tags twice. The working design is: (1) `setResources(localTemplateConf)` runs before `env.render()` so `data.page.view.*` are populated for opt-in templates, (2) `injectAssets(html, data, localOptions)` runs after `env.render()` and auto-injects only when the rendered HTML does NOT already contain the exact stylesheet/script string. User-placement detection is exact-substring match on the final HTML — because `getNodeRes()` emits bundle-specific URLs (hostname + port + webroot), false-positive collisions with unrelated template content are essentially zero. Four additional invariants lock the port: (1) **`injectAssets` runs BEFORE `injectInspectorScripts`** — the Inspector payload (`__ginaData` / `__ginaLogs`) must remain the last `<script>` before `</body>` for dev-mode consumers; reversing the order scatters the payload above user scripts and breaks the flow panel's tail-reading heuristic. (2) **Both `setResources` and `injectAssets` wrap their bodies in try/catch and pass the un-mutated HTML through on any error** — a mis-shaped `localTemplateConf` (common during template migration) must NOT 500 the page. Swig's equivalent mutation runs at compile-time where failures have a separate error channel; the nunjucks pass inspects the production HTML and can't afford to fail. (3) **`isWithoutLayout` filters via `Collection.find({isCommon: false}, {isCommon: true, name: 'gina'})` after `JSON.clone(localTemplateConf)`** — mirrors `render-swig.js:494-498` exactly. The OR-clause keeps private user assets AND common-gina assets, drops common-other; cloning first prevents poisoning the bundle's cached template config object across requests. (4) **HEAD / partial / bodyless templates pass through unchanged** — `injectAssets` only inserts when `</head>` / `</body>` anchors exist in the HTML; it never grows truncated markup. `javascriptsDeferEnabled` controls whether scripts go before `</head>` or before `</body>`; `javascriptsExcluded === '**'` or an already-present `window.onGinaLoaded` suppresses the ginaLoader injection — both mirror swig-side semantics. Locked by +27 source-inspection + behavioural tests in `test/lib/render-engine-dispatch.test.js` §05d (78 → 105 in file, 3419/3419 suite-wide). Precedent: `#NJ2` (2026-04-23) — second of four sessions in the nunjucks parity ASAP track (#NJ1–#NJ4, `0.3.7` roadmap).
849
849
 
850
- 72. **Socket's category flags (`Uses eval`, `Network access`, `Shell access`, `Install scripts`, `Obfuscated code`) are boolean per package — one site fires the flag for the whole tarball.** Before promising to "clear" a flag in a session, enumerate ALL sites in the published tarball — not just the ones visible while reading a single file. The enumeration path: `npm view <pkg>@<ver> dist.tarball` → `curl -sSL <url> -o pkg.tgz` → `tar tzf pkg.tgz` (**never** `npm pack --dry-run` without flagging the cost first — in gina it runs `prepare_version.js` which creates a "Prerelease update" commit). Then grep the extracted file list for the offending pattern, because `.npmignore` + `files:` in `package.json` exclude/include things that greps against the working tree miss. For `Uses eval`: both `\beval\s*\(` and `new\s+Function\s*\(` surface under the same Socket category. Hiding spots in gina: (1) **load-bearing** circular-require workarounds that LOOK like dead test-time fallbacks — `merge = eval(fs.readFileSync('.../merge/src/main.js'))` in `lib/logger/src/main.js:69` + the two logger containers (`file/index.js:16`, `mq/index.js:10`) fires when `require('../../merge')` returns a partial `{}` during the circular chain `merge → helpers → logger → merge-partial`. Eval is load-bearing here because it bypasses the require cache AND relies on `JSON.clone` being defined by the time it executes (so merge's own `typeof(JSON.clone) == 'undefined'` → `require('../../../helpers')` branch is skipped, breaking the circular). Removing the fallback without a runtime probe crashes every logger-consuming test with `TypeError: merge is not a function at init (logger/src/main.js:350)` — hand-on confirmation during the #SCS1c attempt (reverted 2026-04-23). Takeaway: any `eval`/`new Function` site wearing a `// if needed for unit tests` or `// publishing hack` comment is the FIRST suspect for load-bearing status; run the suite with the site commented out before writing a removal commit. (2) install-time scripts that ship via npm lifecycle (`script/pre_install.js`, `script/post_install.js`); (3) vendored client-side code shipped as-is (`core/plugins/lib/validator/src/*`); (4) CLI helpers that chain async work via generated function bodies (`lib/cmd/framework/build.js`, `lib/cmd/framework/init.js`) — same pattern, 4 call sites (CLEARED in #SCS1b, commit `04cd05b2`, 2026-04-23 — direct `promisify` + `try/await/catch` IIFE replaces the eval'd `async function on<Name>()` chain). Count BEFORE claiming a fix will clear a flag; the inverse (claim three, find twenty-one) is a credibility cost you can't redeem mid-session. The **replace-don't-delete convention** (`// #SCS1 (YYYY-MM-DD) — <why>` + commented-out old body on the preceding lines) serves as the navigation aid for future passes: `git grep '#SCS1' framework/v*/ | grep -v '^\s*//'` returns zero hits when every in-file tag has been properly paired with a comment block. Precedent: `#SCS1` (2026-04-23, commit `03eec441`) — three server-side sites replaced (`core/server.js:1870` eval→`new RegExp`; `lib/logger/src/main.js:525` `new Function`→closure factory; `lib/math/index.js:53` `new Function`→shunting-yard evaluator with `+ - * / %`, parens, decimals, unary sign, hard-fails on any non-arithmetic character). Socket's `Uses eval` flag still fires because the 21 remaining sites did not all prove removable: #SCS1b (`04cd05b2`) cleared the 4 async-chain sites cleanly; the 3 logger-container `merge = eval(...)` sites turned out to be load-bearing circular-require workarounds (#SCS1c reverted). The remaining real-work buckets are (a) structural refactor of the `merge → helpers → logger` circular chain so the eval fallback can be deleted without breaking init-time loads, (b) [CLEARED in #SCS1d, 2026-04-23 — `lib/collection/src/main.js` `tryEval` body is now a regex-based binary-compare evaluator over the closed grammar `<operand><op><operand>` (operand ∈ number | "string" | `new Date("...")`, op ∈ `=== !== == != < > <= >=`), and the sibling `eval('_content.'+f)` at line 383 is now a dot-path walker rejecting any non-identifier char. Real RCE vector closed: the line-411 `tryEval(value + filter)` site had no quote-wrapping — a filter like `{'rating.score': '> 0; throw new Error()'}` used to execute. Browser bundle covered for free — `build.json:21` aliases `lib/collection` to the same server-side source, so the Closure-built `dist/vendor/gina/js/gina.min.js` inherits the fix on next rebuild. Fix verified by full 3507/3507 suite + 7 new tests (source-inspection guard, date-compare coverage fill, injection rejection)], and (c) [PARTIAL — #SCS1e, 2026-04-24 — 4 of 11 validator-plugin sites replaced: `main.js:2603` (dot-path walker for `gina.forms.rules[customRule]` lookup, closing the `constructor.constructor("return ...")()` escape on user-controlled `data-gina-form-rule` attributes — 15 real Freelancer corpus values all pure dot-paths after `.replace(/\-|\//g, '.')`); `form-validator.js:161` (dot+bracket walker for `data.x[y]` paths in `{{...}}` error-message placeholders — accepts `ident (. ident | ["quoted"])*` only; Freelancer has zero `{{...}}` in forms/rules JSON, corpus synthetic); `form-validator.js:893` (regex-literal parser + `new RegExp(body, flags).test(value)` — `/<body>/<flags>` only); `form-validator.js:895` (binary-compare evaluator over closed grammar `<operand><op><operand>`, extended from #SCS1d to accept `null`/`undefined`/`true`/`false` literals alongside number/"string" — 15 regex + 11 binary conditions in real Freelancer `is` fields all match the two grammars). Sites deferred pending a design discussion: `form-validator.js:919` (arbitrary condition — no `$var` sub, no paren-strip, grammar unconstrained); `form-validator.js:1722` (`eval('(' + userValidator + ')')` — feature-intrinsic user-defined function body); `main.js:2320`/`:2329` (HTML data-attribute event callbacks); `main.js:2931`/`:2947`/`:2959` (root-path assignment in recursive `makeObjectFromArgs` — needs string-accumulator → segments-array refactor). Four ADDITIONAL sites surfaced during enumeration but out-of-scope for #SCS1e: `core/asset/plugin/src/vendor/gina/toolbar/main.js:1283/:1285` (same `gina.forms.rules[x]` pattern as `:2603`, toolbar-side — unreachable: `toolbar/` source was orphan from the bundle (no `gina/toolbar` alias in `build.json`, no `GinaToolbar` constructor in the built `gina.min.js`), removed in `69fb32fc` 2026-04-24 rather than ported) + `core/asset/plugin/src/vendor/gina/utils/events.js:873/:883` (same HTML event callbacks as main.js:2320/:2329). **Lesson — enumerate by pattern, not by filename**: the #SCS1e session scope was "the 12 validator evals"; actually inspecting the published tarball surfaced 4 more adjacent-pattern sites in toolbar/events that should have been planned into the same session. Next time: `grep -rn "eval\s*(\|new\s\+Function\s*(" framework/v*/ --include='*.js' | grep -vE 'node_modules|asset/plugin/dist'` before committing to a scope. **Lesson — reachability probe before the port**: 2 of the 4 adjacent sites surfaced by that pattern enumeration (`toolbar/main.js:1283/:1285`) were in an orphan module — the `toolbar/` directory had been dropped from `build.json`'s alias map several releases back, and the built `gina.min.js` carried no `GinaToolbar` constructor. A "security port" there was a source-only change that would not have reached any customer (removed in `69fb32fc` 2026-04-24). Before adding an eval site to a session scope, run the reachability probe: confirm the file's path matches an entry in `framework/v*/core/asset/plugin/src/vendor/gina/build.json` `paths` map (after resolving relative-path targets). The five bundled-source patterns enforced by `.githooks/pre-commit` (added 2026-04-24 commit `e459a3de`) are the canonical list — if your file path doesn't match any of them, it's not in the JS bundle: `core/asset/plugin/src/vendor/gina/**`, `core/plugins/lib/(validator|storage)/src/`, `lib/(collection|domain|merge|inherits|uuid|routing)/src/`, `helpers/(prototypes|dateFormat).js`, `helpers/data/src/`. **An earlier draft of this lesson recommended `grep -c '<amd-alias>\|function <Constructor>\|define.*<id>' framework/v*/core/asset/plugin/dist/vendor/gina/js/gina.js` — DO NOT use that recipe**; empirically only 12 of 22 `build.json` aliases produce a direct `define('alias',...)` match in the rebuilt `gina.js` because RequireJS inlines transitive dependencies under file-path module IDs and Closure may collapse define statements. Path-pattern matching against `build.json` is the honest probe. Full orphan check is two probes: (a) the path-pattern check above, (b) `ls framework/v*/core/asset/plugin/src/vendor/gina/<plugin>/sass/` — see #75 for the CSS-only-orphan trap when (a) is no and (b) is yes. **Lesson — security-tagged source commits must rebuild the bundle in the same commit**: `48b8fd26` (#SCS1e) committed the validator walker to `core/plugins/lib/validator/src/*` but did NOT include the Closure-rebuilt `dist/vendor/gina/js/gina.*`. Between `48b8fd26` and `69fb32fc` (which rebuilt as a side effect of toolbar removal) the `gina.min.js` on develop still served the vulnerable `eval('data.' + localValue)` to any browser loading it. `post_publish.js bumpVersion` is a `git mv framework/v{old}/ framework/v{new}/` + version-string rewrite — it does NOT re-run Closure. Rule: any commit modifying source under `core/asset/plugin/src/vendor/gina/**`, `core/plugins/lib/(validator|storage)/src/`, `lib/(collection|domain|merge|inherits|uuid|routing)/src/`, `helpers/(prototypes|dateFormat).js`, or `helpers/data/src/` must stage the rebuilt `dist/vendor/gina/js/gina.*` + `dist/vendor/gina/css/gina.min.css` in the same commit. Pre-flight: `bash framework/v*/core/asset/plugin/build --env=prod` then `git diff --cached framework/v*/core/asset/plugin/dist/` should show the walker/fix inlined. **Mechanical guards (added 2026-04-24)**: `.githooks/pre-commit` (commit `e459a3de`) refuses any commit that modifies the bundled-source patterns above without staging at least one `dist/vendor/gina/(js|css)/gina*` file — cheap "did you stage dist?" check. `.github/workflows/bundle-freshness.yml` (commit `2b6e99ff`) closes the deeper "staged dist is stale" case by rebuilding from the pushed source on every push to develop/master and failing if `git diff dist/` is non-empty — runs in parallel with security.yml / test.yml / vendored-cve.yml. Verified by +69 tests in `test/lib/validator-scs1e.test.js` (source-inspection guards pinning the 4 replaced + 7 deferred sites, behavioural parity against corpus, injection rejection for each primitive). Full suite 3604/3604]. Post-#SCS1e live count in `framework/v*/` (updated 2026-04-24 after orphan `toolbar/` removal in `69fb32fc`): 12 sites — 7 validator-plugin deferred, 2 events.js (HTML callbacks), 3 logger circular-require; 13 of 24 originally-catalogued sites cleared by walker replacement across #SCS1+#SCS1b+#SCS1d+#SCS1e, 2 more removed by deleting orphan source (15 of 24 addressed). The rebuild in `69fb32fc` also picked up the #SCS1e walker that had been committed source-only in `48b8fd26` — the `gina.min.js` between those two commits still served the vulnerable `eval('data.' + localValue)` to any browser loading it.
850
+ 72. **Socket's category flags (`Uses eval`, `Network access`, `Shell access`, `Install scripts`, `Obfuscated code`) are boolean per package — one site fires the flag for the whole tarball.** Before promising to "clear" a flag in a session, enumerate ALL sites in the published tarball — not just the ones visible while reading a single file. The enumeration path: `npm view <pkg>@<ver> dist.tarball` → `curl -sSL <url> -o pkg.tgz` → `tar tzf pkg.tgz` (**never** `npm pack --dry-run` without flagging the cost first — in gina it runs `prepare_version.js` which creates a "Prerelease update" commit). Then grep the extracted file list for the offending pattern, because `.npmignore` + `files:` in `package.json` exclude/include things that greps against the working tree miss. For `Uses eval`: both `\beval\s*\(` and `new\s+Function\s*\(` surface under the same Socket category. Hiding spots in gina: (1) **load-bearing** circular-require workarounds that LOOK like dead test-time fallbacks — `merge = eval(fs.readFileSync('.../merge/src/main.js'))` in `lib/logger/src/main.js:69` + the two logger containers (`file/index.js:16`, `mq/index.js:10`) fires when `require('../../merge')` returns a partial `{}` during the circular chain `merge → helpers → logger → merge-partial`. Eval is load-bearing here because it bypasses the require cache AND relies on `JSON.clone` being defined by the time it executes (so merge's own `typeof(JSON.clone) == 'undefined'` → `require('../../../helpers')` branch is skipped, breaking the circular). Removing the fallback without a runtime probe crashes every logger-consuming test with `TypeError: merge is not a function at init (logger/src/main.js:350)` — hand-on confirmation during the #SCS1c attempt (reverted 2026-04-23). Takeaway: any `eval`/`new Function` site wearing a `// if needed for unit tests` or `// publishing hack` comment is the FIRST suspect for load-bearing status; run the suite with the site commented out before writing a removal commit. (2) install-time scripts that ship via npm lifecycle (`script/pre_install.js`, `script/post_install.js`); (3) vendored client-side code shipped as-is (`core/plugins/lib/validator/src/*`); (4) CLI helpers that chain async work via generated function bodies (`lib/cmd/framework/build.js`, `lib/cmd/framework/init.js`) — same pattern, 4 call sites (CLEARED in #SCS1b, commit `04cd05b2`, 2026-04-23 — direct `promisify` + `try/await/catch` IIFE replaces the eval'd `async function on<Name>()` chain). Count BEFORE claiming a fix will clear a flag; the inverse (claim three, find twenty-one) is a credibility cost you can't redeem mid-session. The **replace-don't-delete convention** (`// #SCS1 (YYYY-MM-DD) — <why>` + commented-out old body on the preceding lines) serves as the navigation aid for future passes: `git grep '#SCS1' framework/v*/ | grep -v '^\s*//'` returns zero hits when every in-file tag has been properly paired with a comment block. Precedent: `#SCS1` (2026-04-23, commit `03eec441`) — three server-side sites replaced (`core/server.js:1870` eval→`new RegExp`; `lib/logger/src/main.js:525` `new Function`→closure factory; `lib/math/index.js:53` `new Function`→shunting-yard evaluator with `+ - * / %`, parens, decimals, unary sign, hard-fails on any non-arithmetic character). Socket's `Uses eval` flag still fires because the 21 remaining sites did not all prove removable: #SCS1b (`04cd05b2`) cleared the 4 async-chain sites cleanly; the 3 logger-container `merge = eval(...)` sites turned out to be load-bearing circular-require workarounds (#SCS1c reverted). The remaining real-work buckets are (a) structural refactor of the `merge → helpers → logger` circular chain so the eval fallback can be deleted without breaking init-time loads, (b) [CLEARED in #SCS1d, 2026-04-23 — `lib/collection/src/main.js` `tryEval` body is now a regex-based binary-compare evaluator over the closed grammar `<operand><op><operand>` (operand ∈ number | "string" | `new Date("...")`, op ∈ `=== !== == != < > <= >=`), and the sibling `eval('_content.'+f)` at line 383 is now a dot-path walker rejecting any non-identifier char. Real RCE vector closed: the line-411 `tryEval(value + filter)` site had no quote-wrapping — a filter like `{'rating.score': '> 0; throw new Error()'}` used to execute. Browser bundle covered for free — `build.json:21` aliases `lib/collection` to the same server-side source, so the Closure-built `dist/vendor/gina/js/gina.min.js` inherits the fix on next rebuild. Fix verified by full 3507/3507 suite + 7 new tests (source-inspection guard, date-compare coverage fill, injection rejection)], and (c) [PARTIAL — #SCS1e, 2026-04-24 — 4 of 11 validator-plugin sites replaced: `main.js:2603` (dot-path walker for `gina.forms.rules[customRule]` lookup, closing the `constructor.constructor("return ...")()` escape on user-controlled `data-gina-form-rule` attributes — 15 real consumer-app corpus values all pure dot-paths after `.replace(/\-|\//g, '.')`); `form-validator.js:161` (dot+bracket walker for `data.x[y]` paths in `{{...}}` error-message placeholders — accepts `ident (. ident | ["quoted"])*` only; the consumer-app has zero `{{...}}` in forms/rules JSON, corpus synthetic); `form-validator.js:893` (regex-literal parser + `new RegExp(body, flags).test(value)` — `/<body>/<flags>` only); `form-validator.js:895` (binary-compare evaluator over closed grammar `<operand><op><operand>`, extended from #SCS1d to accept `null`/`undefined`/`true`/`false` literals alongside number/"string" — 15 regex + 11 binary conditions in real consumer-app `is` fields all match the two grammars). Sites deferred pending a design discussion: `form-validator.js:919` (arbitrary condition — no `$var` sub, no paren-strip, grammar unconstrained); `form-validator.js:1722` (`eval('(' + userValidator + ')')` — feature-intrinsic user-defined function body); `main.js:2320`/`:2329` (HTML data-attribute event callbacks); `main.js:2931`/`:2947`/`:2959` (root-path assignment in recursive `makeObjectFromArgs` — needs string-accumulator → segments-array refactor). Four ADDITIONAL sites surfaced during enumeration but out-of-scope for #SCS1e: `core/asset/plugin/src/vendor/gina/toolbar/main.js:1283/:1285` (same `gina.forms.rules[x]` pattern as `:2603`, toolbar-side — unreachable: `toolbar/` source was orphan from the bundle (no `gina/toolbar` alias in `build.json`, no `GinaToolbar` constructor in the built `gina.min.js`), removed in `69fb32fc` 2026-04-24 rather than ported) + `core/asset/plugin/src/vendor/gina/utils/events.js:873/:883` (same HTML event callbacks as main.js:2320/:2329). **Lesson — enumerate by pattern, not by filename**: the #SCS1e session scope was "the 12 validator evals"; actually inspecting the published tarball surfaced 4 more adjacent-pattern sites in toolbar/events that should have been planned into the same session. Next time: `grep -rn "eval\s*(\|new\s\+Function\s*(" framework/v*/ --include='*.js' | grep -vE 'node_modules|asset/plugin/dist'` before committing to a scope. **Lesson — reachability probe before the port**: 2 of the 4 adjacent sites surfaced by that pattern enumeration (`toolbar/main.js:1283/:1285`) were in an orphan module — the `toolbar/` directory had been dropped from `build.json`'s alias map several releases back, and the built `gina.min.js` carried no `GinaToolbar` constructor. A "security port" there was a source-only change that would not have reached any customer (removed in `69fb32fc` 2026-04-24). Before adding an eval site to a session scope, run the reachability probe: confirm the file's path matches an entry in `framework/v*/core/asset/plugin/src/vendor/gina/build.json` `paths` map (after resolving relative-path targets). The five bundled-source patterns enforced by `.githooks/pre-commit` (added 2026-04-24 commit `e459a3de`) are the canonical list — if your file path doesn't match any of them, it's not in the JS bundle: `core/asset/plugin/src/vendor/gina/**`, `core/plugins/lib/(validator|storage)/src/`, `lib/(collection|domain|merge|inherits|uuid|routing)/src/`, `helpers/(prototypes|dateFormat).js`, `helpers/data/src/`. **An earlier draft of this lesson recommended `grep -c '<amd-alias>\|function <Constructor>\|define.*<id>' framework/v*/core/asset/plugin/dist/vendor/gina/js/gina.js` — DO NOT use that recipe**; empirically only 12 of 22 `build.json` aliases produce a direct `define('alias',...)` match in the rebuilt `gina.js` because RequireJS inlines transitive dependencies under file-path module IDs and Closure may collapse define statements. Path-pattern matching against `build.json` is the honest probe. Full orphan check is two probes: (a) the path-pattern check above, (b) `ls framework/v*/core/asset/plugin/src/vendor/gina/<plugin>/sass/` — see #75 for the CSS-only-orphan trap when (a) is no and (b) is yes. **Lesson — security-tagged source commits must rebuild the bundle in the same commit**: `48b8fd26` (#SCS1e) committed the validator walker to `core/plugins/lib/validator/src/*` but did NOT include the Closure-rebuilt `dist/vendor/gina/js/gina.*`. Between `48b8fd26` and `69fb32fc` (which rebuilt as a side effect of toolbar removal) the `gina.min.js` on develop still served the vulnerable `eval('data.' + localValue)` to any browser loading it. `post_publish.js bumpVersion` is a `git mv framework/v{old}/ framework/v{new}/` + version-string rewrite — it does NOT re-run Closure. Rule: any commit modifying source under `core/asset/plugin/src/vendor/gina/**`, `core/plugins/lib/(validator|storage)/src/`, `lib/(collection|domain|merge|inherits|uuid|routing)/src/`, `helpers/(prototypes|dateFormat).js`, or `helpers/data/src/` must stage the rebuilt `dist/vendor/gina/js/gina.*` + `dist/vendor/gina/css/gina.min.css` in the same commit. Pre-flight: `bash framework/v*/core/asset/plugin/build --env=prod` then `git diff --cached framework/v*/core/asset/plugin/dist/` should show the walker/fix inlined. **Mechanical guards (added 2026-04-24)**: `.githooks/pre-commit` (commit `e459a3de`) refuses any commit that modifies the bundled-source patterns above without staging at least one `dist/vendor/gina/(js|css)/gina*` file — cheap "did you stage dist?" check. `.github/workflows/bundle-freshness.yml` (commit `2b6e99ff`) closes the deeper "staged dist is stale" case by rebuilding from the pushed source on every push to develop/master and failing if `git diff dist/` is non-empty — runs in parallel with security.yml / test.yml / vendored-cve.yml. Verified by +69 tests in `test/lib/validator-scs1e.test.js` (source-inspection guards pinning the 4 replaced + 7 deferred sites, behavioural parity against corpus, injection rejection for each primitive). Full suite 3604/3604]. Post-#SCS1e live count in `framework/v*/` (updated 2026-04-24 after orphan `toolbar/` removal in `69fb32fc`): 12 sites — 7 validator-plugin deferred, 2 events.js (HTML callbacks), 3 logger circular-require; 13 of 24 originally-catalogued sites cleared by walker replacement across #SCS1+#SCS1b+#SCS1d+#SCS1e, 2 more removed by deleting orphan source (15 of 24 addressed). The rebuild in `69fb32fc` also picked up the #SCS1e walker that had been committed source-only in `48b8fd26` — the `gina.min.js` between those two commits still served the vulnerable `eval('data.' + localValue)` to any browser loading it.
851
851
 
852
- 73. **Socket aggregates `peerDependencies` into a package's dependency graph regardless of `peerDependenciesMeta.optional`, and walks sub-`package.json` files inside the published tarball for declared externals.** Two independent supply-chain-visibility leaks surfaced during the 2026-04-23 Socket audit of `gina@0.3.7-alpha.4` — the analyzer reported 11 dependencies when gina actually loads only 3 at runtime (`@rhinostone/swig`, `psl`, `engine.io`). Leak #1: **optional peerDependencies still count.** The root `package.json` declared 8 connector drivers (couchbase, mongodb, mysql2, pg, ioredis, `@scylladb/scylla-driver`, openai, `@anthropic-ai/sdk`) in `peerDependencies` with every entry also listed in `peerDependenciesMeta` as `optional: true`. npm's install output correctly stays silent on optional peers, but Socket + Dependabot + `npm audit` all aggregate `peerDependencies` into the dep graph unconditionally — the `optional` bit is documentation, not a scope boundary. Leak #2: **vendored sub-`package.json` files leak their declared externals.** `framework/v*/core/deps/busboy-1.6.0/package.json` declared `"streamsearch": "^1.1.0"` in its `dependencies` block — but busboy was vendored alongside streamsearch and found it via `require('../../../streamsearch-1.1.0')`, not via `node_modules` resolution. The declared dep was cosmetic at runtime, yet Socket walked into the sub-manifest and surfaced streamsearch as a top-level gina dep. **Enumeration pattern** — before claiming a Socket cleanup, scan every sub-`package.json` in the framework tree for declared externals: `for pkg in $(find framework/v*/ -name 'package.json' -not -path '*/node_modules/*'); do node -e "var p=require('$PWD/$pkg'); var d=Object.assign({},p.dependencies||{},p.peerDependencies||{}); if(Object.keys(d).length) console.log('$pkg:', JSON.stringify(d));"; done`. The ONLY sub-manifest that should show externals is `framework/v*/package.json` itself — anything else is a leak (either a forgotten vendored dep, or a lib package.json that picked up stray fields). **Fix pattern for optional peers** — don't try to hide them via `optional: true`; remove them from `peerDependencies` entirely and move the version ranges to a lib-local registry. Consumers that need the range (the `connector:add --install` path in gina's case) read from the registry, not from `package.json`. ~~Fix pattern for vendored package.json leaks — strip `dependencies`, `devDependencies`, and `scripts` from the vendored manifest.~~ **⚠ CORRECTED 2026-04-24 — this pattern was WRONG and reverted in commit `e5d5d0a2` (alpha.6). Stripping `dependencies` removes the dep-graph edge Socket/Dependabot/`npm audit` follow to associate vendored copies with their CVE records; the metadata looks "dead at runtime" but is load-bearing for supply-chain visibility. See #74 for the correct pattern.** **Credibility trap — the "kept in sync by hand" anti-pattern**: the 8 peerDependencies entries were triple-duplicated (root `package.json` + `lib/cmd/connector/add.js` DRIVER_MAP + `lib/cmd/connector/list.js` DRIVER_MAP), each with a JSDoc comment saying "kept in sync by hand with the framework's peerDependencies". Today's audit confirmed they had already drifted — `list.js` included `mongodb` + `scylladb`, `add.js` did not, `peerDependencies` had all 8. Any "kept in sync by hand" comment across N ≥ 2 files is eventually going to drift; the cure is a shared lib (gina's `lib/connector-registry/` pattern), and the supply-chain cleanup is the natural forcing function. Precedent: #Socket1 (2026-04-23, commits `a3c692f6` peerDeps removal + `4a29ca0c` vendored metadata strip + `5a110822` `lib/connector-registry/` extraction) — dep count dropped from 11 to 3 without any runtime behaviour change; `require('<driver>')` still throws `MODULE_NOT_FOUND` exactly as before when the user's project hasn't installed the driver. Paired with CLAUDE.md's "grep all consumers before changing any shared value" rule: grepping for `peerDependencies` before editing the package.json surfaced 9 cross-file references (JSDoc comments, help.txt, schema description, one test assertion) that all had to be updated in the same commit to keep the story consistent.
852
+ 73. **Socket aggregates `peerDependencies` into a package's dependency graph regardless of `peerDependenciesMeta.optional`, and walks sub-`package.json` files inside the published tarball for declared externals.** Two independent supply-chain-visibility leaks surfaced during the 2026-04-23 Socket audit of `gina@0.3.7-alpha.4` — the analyzer reported 11 dependencies when gina actually loads only 3 at runtime (`@rhinostone/swig`, `psl`, `engine.io`). Leak #1: **optional peerDependencies still count.** The root `package.json` declared 8 connector drivers (couchbase, mongodb, mysql2, pg, ioredis, `@scylladb/scylla-driver`, openai, `@anthropic-ai/sdk`) in `peerDependencies` with every entry also listed in `peerDependenciesMeta` as `optional: true`. npm's install output correctly stays silent on optional peers, but Socket + Dependabot + `npm audit` all aggregate `peerDependencies` into the dep graph unconditionally — the `optional` bit is documentation, not a scope boundary. Leak #2: **vendored sub-`package.json` files leak their declared externals.** `framework/v*/core/deps/busboy-1.6.0/package.json` declared `"streamsearch": "^1.1.0"` in its `dependencies` block — but busboy was vendored alongside streamsearch and found it via `require('../../../streamsearch-1.1.0')`, not via `node_modules` resolution. The declared dep was cosmetic at runtime, yet Socket walked into the sub-manifest and surfaced streamsearch as a top-level gina dep. **Enumeration pattern** — before claiming a Socket cleanup, scan every sub-`package.json` in the framework tree for declared externals: `for pkg in $(find framework/v*/ -name 'package.json' -not -path '*/node_modules/*'); do node -e "var p=require('$PWD/$pkg'); var d=Object.assign({},p.dependencies||{},p.peerDependencies||{}); if(Object.keys(d).length) console.log('$pkg:', JSON.stringify(d));"; done`. The ONLY sub-manifest that should show externals is `framework/v*/package.json` itself — anything else is a leak (either a forgotten vendored dep, or a lib package.json that picked up stray fields). **Fix pattern for optional peers** — don't try to hide them via `optional: true`; remove them from `peerDependencies` entirely and move the version ranges to a lib-local registry. Consumers that need the range (the `connector:add --install` path in gina's case) read from the registry, not from `package.json`. ~~Fix pattern for vendored package.json leaks — strip `dependencies`, `devDependencies`, and `scripts` from the vendored manifest.~~ **⚠ CORRECTED 2026-04-24 — this pattern was WRONG and reverted in commit `e5d5d0a2` (alpha.6). Stripping `dependencies` removes the dep-graph edge Socket/Dependabot/`npm audit` follow to associate vendored copies with their CVE records; the metadata looks "dead at runtime" but is load-bearing for supply-chain visibility. See #74 for the correct pattern.** **Credibility trap — the "kept in sync by hand" anti-pattern**: the 8 peerDependencies entries were triple-duplicated (root `package.json` + `lib/cmd/connector/add.js` DRIVER_MAP + `lib/cmd/connector/list.js` DRIVER_MAP), each with a JSDoc comment saying "kept in sync by hand with the framework's peerDependencies". Today's audit confirmed they had already drifted — `list.js` included `mongodb` + `scylladb`, `add.js` did not, `peerDependencies` had all 8. Any "kept in sync by hand" comment across N ≥ 2 files is eventually going to drift; the cure is a shared lib (gina's `lib/connector-registry/` pattern), and the supply-chain cleanup is the natural forcing function. Precedent: #Socket1 (2026-04-23, commits `a3c692f6` peerDeps removal + `4a29ca0c` vendored metadata strip + `5a110822` `lib/connector-registry/` extraction) — dep count dropped from 11 to 3 without any runtime behaviour change; `require('<driver>')` still throws `MODULE_NOT_FOUND` exactly as before when the user's project hasn't installed the driver. Paired with the global "grep all consumers before changing any shared value" rule: grepping for `peerDependencies` before editing the package.json surfaced 9 cross-file references (JSDoc comments, help.txt, schema description, one test assertion) that all had to be updated in the same commit to keep the story consistent.
853
853
 
854
854
  75. **SASS auto-discovery in `core/asset/plugin/build` is independent of the `build.json` JS alias map — a plugin directory can be orphan on the JS side while still shipping CSS to every customer.** The build script at `framework/v*/core/asset/plugin/build:115-146` walks every `src/vendor/gina/*/sass/` directory and concatenates the compiled CSS into `gina.min.css`, regardless of whether the plugin is still aliased in `build.json` / `build.dev.json`. Concrete incident: `src/vendor/gina/toolbar/` had been dropped from the JS alias map several releases back (the `gina/toolbar` alias was removed from `build.json:30` with a comment documenting the removal), but `toolbar/sass/toolbar.sass` was still being compiled and concatenated — ~22.5 KB of `.gina-toolbar*` rules shipped into every customer's `gina.min.css` with no HTML consuming them. Removed in `69fb32fc` 2026-04-24. **Orphan check for a plugin directory must be three probes, not one**: (a) `grep 'gina/<plugin>' framework/v*/core/asset/plugin/src/vendor/gina/build.json` — if empty, no JS alias; (b) `grep -c 'define.*gina/<plugin>\|function <Constructor>' framework/v*/core/asset/plugin/dist/vendor/gina/js/gina.js` — if 0, not in the built JS bundle; (c) `ls framework/v*/core/asset/plugin/src/vendor/gina/<plugin>/sass/` — if present, CSS still ships. (a) + (b) both absent + (c) present = **CSS-only orphan**: the directory looks dead from the JS side but is still emitting bytes into `gina.min.css`. The fix is `git rm -r src/vendor/gina/<plugin>/` + rebuild with `bash framework/v*/core/asset/plugin/build --env=prod`; alternatively, exclude the plugin from SASS auto-discovery by gating it like `if [ "$f" == "inspector" ]; then continue; fi` at build:144 (current pattern for Inspector, which is served separately at `/_gina/inspector/inspector.css` rather than concatenated). The reverse trap also exists — removing a plugin from `build.json` without removing its `sass/` directory leaves the CSS orphan indefinitely; always do both or neither.
855
855
 
856
856
  76. **Docusaurus pages with `id: <slug>` frontmatter are served at `/<ns>/<id>`, not `/<ns>/<filename>` — external references using filename-form land on 404.** Diagnostic: if a doc URL 404s, probe sibling pages in the same subsection — all siblings 404 + section root (`/<ns>/`) 200 = the `id:` trap, not a missing page. Section-index pages (`index.md`) are immune (no `id:` override). Fix (minimum blast radius): add `slug: /<ns>/<filename>` to each page's frontmatter — keeps the `id:` that other pages reference in cross-links, overrides only the URL. Dropping `id:` also works but requires sweeping every `prereqs`/doc-id reference across the site. Precedent: 2026-04-24 link-health audit surfaced 11 × 404 across `/docs/cli/*` at filename-form URLs (all pages resolve at `/docs/cli/cli-<filename>` because of `id: cli-<name>`) — filed as gina-io/docs#11.
857
857
 
858
- 74. **Vendored-dep `package.json` metadata carries CVE-visibility — never strip `dependencies`.** Supersedes #73's "strip" fix pattern. Vendored deps under `framework/v*/core/deps/` are supply-chain surface identical to npm-installed deps: Socket, Dependabot, and `npm audit` all read the sub-`package.json` files inside the published tarball, extract `(name, version)` pairs, follow `dependencies` edges, and match each against their CVE databases. Stripping `dependencies` (or `devDependencies`, or `scripts`) to "reduce Socket's dep count" removes that signal — even when the fields are "dead at runtime" because the vendored code resolves sibling deps via relative-path `require()`. The metadata is load-bearing for security tooling. **The invariant**: vendored `package.json` stays byte-identical to the upstream npm tarball until a local patch is applied. No strips, no reorders, no cleanups. **Patch discipline**: when gina needs to modify a vendored dep's source, bump its `version` from `<upstream>` to `<upstream>-rhinostone.N` (e.g. `1.6.0-rhinostone.1`). Semver-compliant (it's a prerelease identifier), so OSV/Socket/Dependabot's range-matching still hits the base upstream CVE record; the suffix is a read-the-tree-and-know-at-a-glance signal that the copy has diverged. Reset the counter on any upstream version bump. **Belt-and-suspenders**: `.github/workflows/vendored-cve.yml` runs a Node script (`.github/scripts/scan-vendored-cves.js`) on every push + weekly Sunday cron that walks `framework/v*/core/deps/*/package.json`, queries `api.osv.dev/v1/query` for each `(name, version)` pair (normalising away the `-rhinostone.N` suffix before querying), and fails the build on any matched vulnerability. Independent of whichever third-party scanner happens to be active on the published tarball — if Socket/Dependabot ever shifts methodology again, CVE coverage on vendored deps is still guaranteed locally. **Precedent**: `gina@0.3.7-alpha.5` stripped the vendored metadata per #73's (incorrect) advice — flagged the same day by a maintainer concern that `core/deps/` was no longer being scanned. Reverted in alpha.6 (commit `e5d5d0a2`) along with `.github/workflows/vendored-cve.yml` + `.claude/architecture/vendored-deps.md` documenting the discipline. Socket count went from 3 back to 4 (streamsearch edge restored); every entry is a truthful dep the scanners SHOULD see. **Key reframe**: supply-chain surface is not dep-count minimization — it's whether the scanners have the signal they need. An accurate count with CVE visibility beats an under-reported count with blind spots every time.
858
+ 74. **Vendored-dep `package.json` metadata carries CVE-visibility — never strip `dependencies`.** Supersedes #73's "strip" fix pattern. Vendored deps under `framework/v*/core/deps/` are supply-chain surface identical to npm-installed deps: Socket, Dependabot, and `npm audit` all read the sub-`package.json` files inside the published tarball, extract `(name, version)` pairs, follow `dependencies` edges, and match each against their CVE databases. Stripping `dependencies` (or `devDependencies`, or `scripts`) to "reduce Socket's dep count" removes that signal — even when the fields are "dead at runtime" because the vendored code resolves sibling deps via relative-path `require()`. The metadata is load-bearing for security tooling. **The invariant**: vendored `package.json` stays byte-identical to the upstream npm tarball until a local patch is applied. No strips, no reorders, no cleanups. **Patch discipline**: when gina needs to modify a vendored dep's source, bump its `version` from `<upstream>` to `<upstream>-rhinostone.N` (e.g. `1.6.0-rhinostone.1`). Semver-compliant (it's a prerelease identifier), so OSV/Socket/Dependabot's range-matching still hits the base upstream CVE record; the suffix is a read-the-tree-and-know-at-a-glance signal that the copy has diverged. Reset the counter on any upstream version bump. **Belt-and-suspenders**: `.github/workflows/vendored-cve.yml` runs a Node script (`.github/scripts/scan-vendored-cves.js`) on every push + weekly Sunday cron that walks `framework/v*/core/deps/*/package.json`, queries `api.osv.dev/v1/query` for each `(name, version)` pair (normalising away the `-rhinostone.N` suffix before querying), and fails the build on any matched vulnerability. Independent of whichever third-party scanner happens to be active on the published tarball — if Socket/Dependabot ever shifts methodology again, CVE coverage on vendored deps is still guaranteed locally. **Precedent**: `gina@0.3.7-alpha.5` stripped the vendored metadata per #73's (incorrect) advice — flagged the same day by a maintainer concern that `core/deps/` was no longer being scanned. Reverted in alpha.6 (commit `e5d5d0a2`) along with `.github/workflows/vendored-cve.yml` + internal architecture docs documenting the discipline. Socket count went from 3 back to 4 (streamsearch edge restored); every entry is a truthful dep the scanners SHOULD see. **Key reframe**: supply-chain surface is not dep-count minimization — it's whether the scanners have the signal they need. An accurate count with CVE visibility beats an under-reported count with blind spots every time.
859
859
 
860
- 77. **Session cookies in gina are NOT framework-owned — they are issued by the bundle's own `app.use(session({...}))` call via `express-session`, so framework-level cookie hardening cannot be transparent without regressing intentional bundle choices.** The framework's `lib.SessionStore(session)` factory is a thin wrapper that receives the bundle's `express-session` module reference, reads `connectors.json[session.name].connector`, and returns a connector-specific Store class. It never holds a reference to the cookie options, and it runs once per bundle boot — long after the bundle's `index.js` has already captured `var session = require('express-session')` as a local. Nothing in `core/server.js`, `core/server.isaac.js`, or `core/server.express.js` writes `Set-Cookie` — those three files have zero grep matches on `cookie` and `set-cookie`. **Chokepoint analysis trap**: the single shared response entry at `core/server.js:2324` (`self.instance.all('*', function onInstance(request, response, next) { ... })`) does look like the natural place to wrap `response.setHeader` to post-process every `Set-Cookie` value, and Isaac + Express both route through it. But Set-Cookie strings carry no provenance: `Set-Cookie: sessionid=abc; Path=/; Secure` could be a bundle that never thought about `HttpOnly` (safe to add) OR a bundle that explicitly set `httpOnly: false` because a client-side validator or toolbar has to read `document.cookie` (adding `HttpOnly` breaks it). Inspecting three real bundles (`~/Sites/freelancer/v3/src/{auth,dashboard,public}/index.js`) showed all three deliberately set `httpOnly: false` and comment out `sameSite` — a transparent wrap would have silently regressed every one. This is exactly the failure mode CLAUDE.md's "Don't strip what you haven't surveyed" rule warns about, in its dual form: "don't ADD what the source didn't ask for either". **The correct shape for a cookie-hardening feature** is an opt-in plugin at `core/plugins/lib/session/src/main.js` that wraps `expressSession(options)` with a factory: reads `settings.json > session.cookie.{sameSite, httpOnly, secure}`, merges defaults into `options.cookie` only for flags the caller did NOT set (guarded by `Object.prototype.hasOwnProperty.call(caller, key)`), validates the browser-parity invariant (`SameSite=None` without `Secure` throws), and passes through. Adoption is a one-line swap in the bundle bootstrap: `var session = require('gina').plugins.Session(require('express-session'))`. Existing bundles that don't adopt continue working exactly as before; adopting is explicit and visible in the diff. **The measurement step that surfaced the trap**: before writing any code, grep real bundle code for cookie flag patterns — `grep -nE 'httpOnly|sameSite|secure[\s:]' ~/Sites/<project>/src/*/index.js`. If any bundle has deliberate `httpOnly: false` or `sameSite` commented out, transparent wrapping is off the table and the feature must be opt-in. Precedent: #CSRF1 (2026-04-24, shipped in `0.3.7-alpha.8`) — initial design pivoted from per-request `response.setHeader` wrap to opt-in `gina.plugins.Session` plugin after the Freelancer measurement showed three bundles with deliberate `httpOnly: false`. The plugin shape also becomes the natural seam for #CSRF2 (signed double-submit token middleware, planned for `0.3.8`), which needs a session-aware injection point — session hardening and CSRF token plumbing share the same mount scope.
860
+ 77. **Session cookies in gina are NOT framework-owned — they are issued by the bundle's own `app.use(session({...}))` call via `express-session`, so framework-level cookie hardening cannot be transparent without regressing intentional bundle choices.** The framework's `lib.SessionStore(session)` factory is a thin wrapper that receives the bundle's `express-session` module reference, reads `connectors.json[session.name].connector`, and returns a connector-specific Store class. It never holds a reference to the cookie options, and it runs once per bundle boot — long after the bundle's `index.js` has already captured `var session = require('express-session')` as a local. Nothing in `core/server.js`, `core/server.isaac.js`, or `core/server.express.js` writes `Set-Cookie` — those three files have zero grep matches on `cookie` and `set-cookie`. **Chokepoint analysis trap**: the single shared response entry at `core/server.js:2324` (`self.instance.all('*', function onInstance(request, response, next) { ... })`) does look like the natural place to wrap `response.setHeader` to post-process every `Set-Cookie` value, and Isaac + Express both route through it. But Set-Cookie strings carry no provenance: `Set-Cookie: sessionid=abc; Path=/; Secure` could be a bundle that never thought about `HttpOnly` (safe to add) OR a bundle that explicitly set `httpOnly: false` because a client-side validator or toolbar has to read `document.cookie` (adding `HttpOnly` breaks it). Inspecting three real bundles (`~/Sites/<consumer-app>/src/<bundle>/index.js`) showed all three deliberately set `httpOnly: false` and comment out `sameSite` — a transparent wrap would have silently regressed every one. This is exactly the failure mode the global "Don't strip what you haven't surveyed" rule warns about, in its dual form: "don't ADD what the source didn't ask for either". **The correct shape for a cookie-hardening feature** is an opt-in plugin at `core/plugins/lib/session/src/main.js` that wraps `expressSession(options)` with a factory: reads `settings.json > session.cookie.{sameSite, httpOnly, secure}`, merges defaults into `options.cookie` only for flags the caller did NOT set (guarded by `Object.prototype.hasOwnProperty.call(caller, key)`), validates the browser-parity invariant (`SameSite=None` without `Secure` throws), and passes through. Adoption is a one-line swap in the bundle bootstrap: `var session = require('gina').plugins.Session(require('express-session'))`. Existing bundles that don't adopt continue working exactly as before; adopting is explicit and visible in the diff. **The measurement step that surfaced the trap**: before writing any code, grep real bundle code for cookie flag patterns — `grep -nE 'httpOnly|sameSite|secure[\s:]' ~/Sites/<project>/src/*/index.js`. If any bundle has deliberate `httpOnly: false` or `sameSite` commented out, transparent wrapping is off the table and the feature must be opt-in. Precedent: #CSRF1 (2026-04-24, shipped in `0.3.7-alpha.8`) — initial design pivoted from per-request `response.setHeader` wrap to opt-in `gina.plugins.Session` plugin after the consumer-app measurement showed three bundles with deliberate `httpOnly: false`. The plugin shape also becomes the natural seam for #CSRF2 (signed double-submit token middleware, planned for `0.3.8`), which needs a session-aware injection point — session hardening and CSRF token plumbing share the same mount scope.
861
861
 
862
862
  78. **`git filter-repo` operational gotchas — second-run prompts, origin removal, committer-date SHA churn, and the local-only-tag false positive that makes a previously-cleaned repo *look* like it still has leaks.** Four traps in order: (1) **prior-run safety prompt** — if `.git/filter-repo/already_ran` exists from an earlier rewrite, the next run prints "Treat this run as a continuation of filtering in the previous run (Y/N)?" and reads stdin. `--force` does not bypass it. Pipe `echo Y |` for non-interactive runs. (2) **origin removed** — filter-repo deletes the `origin` remote at the end of every run by design ("so you don't accidentally push the rewritten history"). After the rewrite, `git remote add origin https://github.com/<owner>/<repo>.git` before any push. (3) **committer-date SHA churn** — filter-repo updates committer dates to "now" on every commit it walks, even when message and tree are unchanged. Every reachable commit gets a new SHA. Net effect: a no-op rewrite of an already-clean branch still produces a force-push divergence between local and origin. (4) **local-only-tag false positive** — `git log --all` reaches every ref, including local-only safety-net tags created by prior rewrites (`pre-rewrite-*-DATE`, `pre-history-scrub-*-DATE`). Those tags preserve un-substituted commits as personal backups. They are NOT on origin (verify with `git ls-remote origin 'refs/tags/pre-*'`) and therefore not violating the public-surface rule. Before re-running filter-repo on a previously-cleaned repo, check leak counts per branch with `git log <ref> --format='%s%n%b' | grep -iE '<patterns>' | wc -l` for each live branch individually — if all are zero, the live branches are clean and the only "leaks" left are in personal local-only tags that don't need rewriting. Precedent: 2026-04-25 second-pass leak scrub on gina-io/gina — comprehensive `git log --all` audit found 4 commit subjects + 3 commit bodies with descriptive feature mentions, but per-branch checks showed origin develop/master/dev/wip all already at 0 leaks. The remaining matches lived only in `pre-rewrite-*-20260421T233346Z` tags from a 2026-04-21 prior filter-repo. Today's filter-repo did push develop and dev/wip with cosmetically-different SHAs (force-pushed for nothing) before the per-branch verification surfaced that master was already clean. The reflex "saw leaks via `git log --all`, run filter-repo" needs the per-branch refinement step to avoid unnecessary force-push churn on tracking clones.
863
863
 
@@ -871,6 +871,20 @@ YAML indent trap: the heredoc body and the `EOF` terminator must land at column
871
871
 
872
872
  83. **`Origin: "null"` is a real value browsers send — sandboxed iframes (`<iframe sandbox>`), `file://` pages, and some redirected requests carry the literal string `"null"` in the Origin header (and in `Sec-Fetch-Site: cross-site` flows).** Origin-checking code that does `if (origin) { /* allowlist match */ }` treats `"null"` as truthy and tries to match it. Without an explicit guard the result is wrong-for-the-right-outcome at best: the allowlist almost never contains `"null"`, so the request 403s — but for the wrong reason (it should fall through to the Referer fallback or a known-no-origin handler, not be rejected for "origin not in allowlist"). The same trap applies to CORS allowlists, CSRF Origin pre-filters, audit logging, and any code branching on `Origin`. **Fix**: add an explicit `s === 'null'` guard at the start of the Origin parser, treating `"null"` exactly like an absent header — empty result, fall through to the next signal (Referer, Sec-Fetch metadata, etc.). Document the guard with a brief comment so it isn't "cleaned up" by a future contributor who reads it as a no-op string check. Precedent: 2026-04-26 #CSRF3 implementation — `parseOriginString` at `core/plugins/lib/csrf/src/main.js:108` returns `null` (the JS literal) when input is the string `"null"`, so `parseRequestOrigin` falls back to parsing the host out of `Referer`. Two unit tests pin this behaviour: `Origin: "null"` + missing `Referer` → 403 with reason "missing origin/referer"; `Origin: "null"` + valid `Referer` → next() called. Without the guard, the missing-Referer case would have 403'd with reason "origin not allowed", masking the real problem from any operator reading the log.
873
873
 
874
- 84. **Local-tool doc files at gitignored paths drift independently across worktree splits — git merges don't carry gitignored content, so each worktree's local docs must be maintained separately.** A primary repo (`develop`) and a linked worktree (`dev/wip` via `git worktree add`) hold independent gitignored doc copies; a version bump on the primary updates its local docs via tooling, but the linked worktree's copies stay at the previous version because gitignored files don't FF across `git merge --ff-only`. **Auto-update on the primary worktree**: version-bump scripts read `script/.local-sync-targets.json` (gitignored — lists relative paths to local doc files), iterate entries, regex-replace `v<old>` → `v<new>` with `(?![\\d.])` lookahead to avoid clobbering longer prefix-sharing versions. Tracked source code names only the generic config file, never the targets — no public-surface leak of local-tool path names. Same indirection already in `script/post_publish.js → bumpVersion`; replicated in `script/prepare_version.js` for non-publish bumps (added 2026-04-29). **Manual update on linked worktrees**: the project's gitignored git-workflow doc gets a post-merge state-check step instructing the maintainer to grep version refs after every fast-forward and update mismatches. **Generalises**: any worktree-split repo with gitignored doc files has the same pattern. Each worktree's local-doc version refs match its OWN `package.json` — versions can legitimately differ between branches at different cuts. Precedent: 2026-04-29 — discovered drift in two local-doc files (primary on `0.3.9-alpha.2` had docs saying `alpha.11`; linked on `0.3.7-alpha.10` had docs saying `alpha.8`); fixed manually, then automated via the existing `.local-sync-targets.json` indirection.
874
+ 84. **Local-tool doc files at gitignored paths drift independently across worktree splits — git merges don't carry gitignored content, so each worktree's local docs must be maintained separately.** A primary repo (`develop`) and a linked worktree (`dev/wip` via `git worktree add`) hold independent gitignored doc copies; a version bump on the primary updates its local docs via tooling, but the linked worktree's copies stay at the previous version because gitignored files don't FF across `git merge --ff-only`. **Auto-update on the primary worktree**: version-bump scripts read `script/.local-sync-targets.json` (gitignored — lists relative paths to local doc files), iterate entries, regex-replace `v<old>` → `v<new>` with `(?![\\d.])` lookahead to avoid clobbering longer prefix-sharing versions. Tracked source code names only the generic config file, never the targets — no public-surface leak of local-tool path names. Same indirection already in `script/post_publish.js → bumpVersion`; replicated in `script/prepare_version.js` for non-publish bumps (added 2026-04-29). **Constraint to know before extending the sidecar**: the regex requires a literal `v` prefix on the version. Bare version mentions — `0.3.7`, `## What's in 0.3.7`, third-party version strings like `@rhinostone/swig 1.5.0` — are invisible to the mechanism and will not be auto-rewritten. Adding such a file to the sidecar no-ops on those refs; they need a separate maintenance approach. Auto-rewriting bare version mentions would also be strictly worse than no-op — silently relabelling old content as new masks the staleness — so a freshness gate that asserts the file was touched at all in the release window (see §88) is the right primary mechanism for this class of drift. **Manual update on linked worktrees**: the project's gitignored git-workflow doc gets a post-merge state-check step instructing the maintainer to grep version refs after every fast-forward and update mismatches. **Generalises**: any worktree-split repo with gitignored doc files has the same pattern. Each worktree's local-doc version refs match its OWN `package.json` — versions can legitimately differ between branches at different cuts. Precedent: 2026-04-29 — discovered drift in two local-doc files (primary on `0.3.9-alpha.2` had docs saying `alpha.11`; linked on `0.3.7-alpha.10` had docs saying `alpha.8`); fixed manually, then automated via the existing `.local-sync-targets.json` indirection.
875
875
 
876
876
  85. **Tracked source code that reads/writes local-tool files must use a gitignored config-file indirection — naming the local-tool paths directly in tracked source CONTENT is a public-surface leak even when the operation is functional.** Extends entry #68's "local-tool path leaks have three surfaces" by adding a fourth: tracked-script body content (path string literals, code comments, variable names, log/console message strings). **Counterfactual check before writing**: would removing every mention of the local-tool name BREAK the feature, or just make the path configurable? If the latter, the mention is not load-bearing — extract to a gitignored sidecar config the script reads at runtime. Compare to the existing leak-filter regex in `script/prepare_version.js` (already quoted by entry #68) — THAT pattern IS product functionality (the filter literally needs to identify the path to filter it out), allowed exception. **Existing pattern to mirror**: `script/post_publish.js → bumpVersion` reads `script/.local-sync-targets.json` (gitignored — lists relative paths to operate on), iterates entries, applies generic regex-replace. Tracked source names only the generic config file, never the targets. The same shape was replicated in `script/prepare_version.js` (entry #84) after a first-attempt mistake. **The "Don't strip what you haven't surveyed" mirror — "Don't ADD a public-surface mention without surveying the rule first"**. Recovery cost is asymmetric: removal requires `git filter-repo` + force-push (and force-push to a public default branch is itself a high-friction operation per entry #79), addition is one keystroke. Always run the leak-scan regex (per entry #68's documented patterns) against the diff BEFORE staging — `git diff <file> | grep '^+' | grep -v '^+++' | grep -iE '<leak-patterns>'` — not after pushing. Precedent: 2026-04-29 — first attempt at extending `prepare_version.js` for the worktree-drift fix (entry #84) named the local-doc path directly in 6+ places (path literal, two comments, variable names, two log strings); user surfaced the violation before commit during a "are you sure there won't be side effects?" review pass; reverted to the `post_publish.js` indirection pattern in the same session, then committed clean.
877
+
878
+ 86. **Cwd persists across the local-tool harness's Bash tool calls — `cd <path>` inside one bash chain affects every subsequent Bash call until another `cd` resets it.** The Bash tool maintains the shell's working directory between invocations (per its documented "The working directory persists between commands" behaviour). A single chained call like `cd ~/Sites/<other-repo> && npm pkg set ...` leaves the cwd at `~/Sites/<other-repo>` for the NEXT Bash call too — even if the next call's natural read is a different repo. **Symptoms when this bites**: `git rev-parse HEAD` returning a SHA from the wrong repo; `npm publish` packaging the wrong package's `package.json`; `git status` showing no diff because we're checking the wrong repo; `node --test` failing to find files relative to the wrong cwd. **Detection**: `pwd` at the start of a Bash call surfaces the actual cwd; cross-check against the expected primary working directory whenever a multi-step flow has touched another path. **Discipline that prevents it**: prefer `git -C <path>` for git operations on non-cwd repos; prefer absolute paths in `cp`, `npm install`, `node --test`; reserve `cd` for cases where the user explicitly requests it OR where multiple sequential commands must run from one non-default location AND a `cd <primary>` reset follows in the same bash chain. **Cost asymmetry**: the bug is silent — commands succeed but operate on the wrong repo, producing wrong-but-coherent output that doesn't trip an obvious failure. Consequence is shipping the wrong artefact (e.g. publishing the wrong package metadata thinking you published the primary one). Precedent: 2026-05-04 0.3.9 release prep — `cd ~/Sites/gina/docs/repo && npm pkg set ...` left cwd in docs-repo; subsequent `git rev-parse HEAD` returned the docs-repo SHA when the expectation was the gina-repo SHA. Caught before `npm publish` would have published from the wrong cwd; recovered with explicit `cd ~/.npm-global/lib/node_modules/gina && pwd && ...` to restore.
879
+
880
+ 87. **The docs-repo lockfile-mismatch after stable publish is a recurring failure mode of `script/post_publish.js → syncDocs`, not a one-off.** **RESOLVED 2026-05-05 (commit `9f42fb00`):** shape (a) shipped — `script/retry_lockfile_sync.js` exposes `retryWithBackoff(opts)` (pure helper with injected `execDriver` / `sleepDriver` / `logger` for unit testing); `post_publish.js → syncDocs` retries the `npm install --package-lock-only --ignore-scripts` line 4 times with sleeps `[5, 15, 30, 30]` between failures (~80s ceiling); existing non-fatal `console.warn` retained on total exhaustion so the rest of the publish chain (`tagAndMerge` / `bumpVersion` / `publishAlpha`) still runs. 8 new behavioural tests in `test/lib/retry-lockfile-sync.test.js` cover first-attempt success, retry-then-succeed, exhaustion, default delay schedule, input validation, and log-message-brevity (single-line summary of multi-line errors). Validation pending the next stable cut — until then the manual recovery recipe below is the fallback if the registry-eventual-consistency window happens to exceed 80s. _Original entry (kept for context):_ The script bumps `~/Sites/gina/docs/repo/package.json`'s gina devDep to the new version AFTER the publish call succeeded, then runs `npm install --package-lock-only --ignore-scripts` to update `package-lock.json`. The npm registry's eventual-consistency window can leave the just-published version not-yet-resolvable when the lockfile-sync runs immediately after — the call fails non-fatally (`info` log, not `error`), and the merge to docs `main` lands a mismatched `package.json: gina@<new>` + `package-lock.json: gina@<old>` pair. Vercel's `npm ci` then bails with `Invalid: lock file's gina@<old> does not satisfy gina@<new>`, leaving the docs site stuck on the previous version's content until somebody intervenes. **Confirmed occurrences**: 0.3.7 stable (2026-04-26 — flagged in todo/index.md as "non-fatal — fix manually" and assumed transient); 0.3.9 stable (2026-05-04 — same shape, recurred). 0.3.8 dodged because it was a same-day hotfix on top of 0.3.7 with no version-bump-and-republish gap. Two same-shape occurrences on a deterministic ordering bug = pattern, per llms.txt §81 watchlist rule. **Manual recovery (~30s)**: `cd ~/Sites/gina/docs/repo && npm install --package-lock-only --ignore-scripts && git commit -am "Updating package-lock for gina@<new>" && git checkout main && git merge --ff-only develop && git push origin main && git checkout develop`. Verify Vercel redeploy via `gh run list --repo gina-io/docs --workflow update-alias.yml --branch main --limit 1`. **Fix-shape options for `script/post_publish.js`**: (a) retry the lockfile-sync with backoff (5s/15s/30s) gated on a successful `npm view gina@<version>` first, capped at ~60s — registry consistency usually settles within that window; (b) move the lockfile-sync AFTER the develop → main merge and emit a follow-up `Updating package-lock for gina@<version>` commit — turns the recovery into a one-commit catch-up that the next session can land cleanly; (c) bump the failure log from `info` to `error` so the publishing engineer sees a red marker instead of a buried warning. **Until that fix lands, every stable publish session must add to its post-publish verification checklist**: `gh run list --repo gina-io/docs --workflow update-alias.yml --branch main --limit 1` after the publish chain completes; if the latest run's conclusion is `failure`, fall through to the manual recovery recipe above. The lockfile mismatch surfaces with the exact `npm error Invalid: lock file's gina@<old> does not satisfy gina@<new>` text in `gh run view <id> --log-failed`, so the diagnosis is unambiguous when it happens. Discovered 2026-05-04 during the 0.3.9 publish — the second occurrence promoted this from "transient registry lag" to "deterministic ordering bug worth fixing in `post_publish.js`".
881
+
882
+ 88. **Recurring manual-checklist failures need a code-level gate, not another doc reminder — and the gate's primary assertion should be a "did the operator touch this surface at all?" signal, not a content-shape match.** Pattern: a manual checklist step (e.g. "verify README's `## What's in <version>` heading matches the targeted version") gets skipped on three consecutive stable cuts (v0.3.7 → v0.3.8 → v0.3.9 each shipped to npm with a stale `## What's in 0.3.7` heading and a stale `@rhinostone/swig 1.5.0` line, while npm had already advanced to swig `1.6.0`). The skipped-three-times-in-a-row signal is unambiguous: the manual gate is failing, no amount of additional doc text will fix it. **The fix shape**: a fail-closed gate inside `script/prepare_version.js` (alongside `checkNoLocalLeakage` and `checkPrivateTokenLeakage`) that runs on every stable publish — alpha cuts are intermediate and skip the gate. **The right primary assertion is "was README.md touched at all in this release window?", not "does the heading match the new version exactly".** The touch-since-tag check (`git log <previous-stable-tag>..HEAD -- README.md` non-empty) catches every form of forgetfulness — heading drift, bullet drift, swig version drift, badge drift, anything else the user later adds to the file. A heading-match-only gate is strictly weaker (fails when content drifts but heading happens to match) and an auto-bump of the heading regex would be strictly worse — silently relabelling old content as new masks the lie. **The shape that works**: pure helpers in a sibling script (`script/check_readme_freshness.js`, mirrors `script/check_no_local_leak.js`'s sibling pattern), wired into `prepare_version.js` via a `self.checkReadmeFreshness` method that imports the helper. Helpers exported via `module.exports` for unit testing with an injected `gitExec` driver — no real-repo fixtures needed. **Generalises to any "checklist step skipped three times in a row" pattern in any project**: don't write a fourth doc reminder, write a code-level gate. Pick the loosest assertion that catches the forgetfulness signal — "did the surface get touched" is almost always the right primary question, with stricter content checks deferred to belt-and-suspenders if needed. **Operator-facing error messaging discipline**: the gate's stderr names the previous stable tag, the targeted version, and the typical edit shapes — without referencing the gitignored local-tool playbook by path (per the public-surface naming rule, entry #85). The operator knows their own playbook; the script's job is to surface the failure clearly. Established 2026-05-05 after the v0.3.7 → v0.3.8 → v0.3.9 README-staleness pattern was surfaced by a user noting "you forget to update the README.md every time we publish a stable version".
883
+
884
+ 90. **Socket's "Obfuscated code" alert on `gina.min.js` is a verified false positive (Mozilla Public Suffix List data table from `psl@^1.15.0`, byte-identical-data-shape to the standalone `psl@1.15.0` package which Socket scores 100/100) — dismiss per-version on Socket UI with the standing justification text in the local-tool playbook's Socket section. DO NOT attempt the build-pipeline lazy-load (r.js `exclude` of `vendor/gina/psl` + standalone PSL chunk + runtime AMD path config in `core.js`) — it breaks the consumer sync-require contract: gina-consuming bundles routinely call `require('gina/link')` synchronously inside their `onGinaReady` callback, and that contract cannot be satisfied once `lib/domain → vendor/gina/psl` becomes an async XHR (`lib/domain` never resolves → `gina/link` never resolves → consumer hits `Module name "gina/link" has not been loaded yet for context: _`). Verified failure during browser smoke against a representative consumer bundle on 2026-05-05.** A source-level lazy-load (modify `lib/domain/src/main.js` to drop `vendor/gina/psl` as an AMD dep and defer `require(['vendor/gina/psl'])` into the `Domain(cb)` constructor's already-async callback path) would work but the engineering cost (4-8 hrs + cross-route smoke across each of the consumer's nginx static docroots + ongoing standalone-chunk deploy sync) doesn't pay back the per-version dismiss friction (~5-25 min/year). The standing decision is to dismiss per-version. The post-publish dismissal step is wired into both Alpha and Stable Release Checklists in the local-tool playbook.
885
+
886
+ 89. **Lead-side hedging via "open follow-up questions" in design recommendations is a distinct failure mode of the measure-first / no-static-analogy rule — even AFTER doing the measurement, the output shape can still punt the synthesis the recommender owes.** Pattern: investigation done (gap analysis, regex inspection, git-log windows, code-path verification), recommendation written, but it ends with "(i) do you want strict X or relaxed Y? (ii) for the secondary check, signal A or signal B?" — two-options framing that looks measured but is actually pushing the synthesis back to the user. **Detection**: open follow-up questions of the form "X vs Y — which do you want?" at the end of a recommendation are almost always hedging. The recommender has the data; if the data does not discriminate, the answer is "not enough data, would need observation Z to resolve" — not "you pick". **The fix shape**: pick the loosest-but-sufficient assertion the data supports, name explicitly what would change your mind (a falsifying observation, a constraint shift), and commit. If a tradeoff genuinely needs user input (a policy choice between strict-and-friction vs lenient-and-flexible), frame it as "I recommend X for these reasons; the only reason to pick Y is if [explicit constraint] applies — does it?" — that is still committed reasoning, with the user's input narrowed to the one variable that actually moves the answer. **Generalises to any agent or lead recommendation in any project.** This trap operates one layer up from the lead-side static-analogy reasoning rule (already documented as a session rule): static-analogy is "I skipped measurement and reasoned by analogy"; this is "I did measure, but my output shape still hedges". The user's reflex "what's your measured take?" catches both. Rule of thumb: a measured recommendation that ends with two open questions about which design knob to turn has not finished the work. Precedent: 2026-05-05 README freshness gate session — first recommendation flagged for static-analogy reasoning (proposed extending the existing `.local-sync-targets.json` mechanism to README.md, by analogy from the local-doc auto-bump pattern, without measuring whether the regex actually applied to README's bare-version mentions). After the user's "is this measured?" prompt, the second recommendation did the measurement (read README.md, ran git log against the tag pairs, inspected the regex, reviewed the existing checklist) but ended with "(i) strict heading-match vs lag-one-minor — which? (ii) compare swig against npm-resolved or against framework dep — which?". The user's "what's your measured take?" prompted a corrected version that dropped both questions: primary gate is touch-since-tag (catches every form of forgetfulness), secondary heading/swig assertions skipped as redundant. Both layers of the same trap surfaced in one session — useful worked example for the next one.
887
+ 91. **Documented hard rules that have leaked twice on the same surface need a deterministic enforcement gate, not a third doc reminder.** Pattern: a text-only HARD RULE about public-surface content (e.g. "no AI-attribution footers in PR comments", "no consumer-app references in framework source") leaks despite being read every turn, the rule's doc gets reinforced after the first leak, and the same rule leaks again on the same surface within weeks. Same shape as #88 (README freshness gate after three skipped checklist steps). **The fix shape**: a harness-level pre-write hook that intercepts public-write tool calls — `gh issue|pr|release create|edit|comment`, `gh api PATCH/POST` to `issues/pulls/releases` endpoints, `git commit -m`, `git tag -m` — reads stdin JSON for the bash command, scans the inline command body plus any `-F body=@/path` referenced files against the forbidden-pattern regex, and exits non-zero with a stderr explanation on match. Bypass-resistant because the harness runs the hook regardless of operator attention. **Operator-facing recipes**: ship a `--test` self-test mode that runs known-leak fixtures through the matcher and asserts they all block — re-run after every regex tweak. Audit-log every block (`<hook-dir>/<hook-name>.log` append-only) so future surfaces can grep for missed patterns. Allowlist exemptions documented inline (e.g. AI-connector vendor names like `@<vendor>-ai/sdk`, `<VENDOR>_API_KEY`, `<vendor>://` protocol identifiers that legitimately mention the vendor). **Generalises to any public-surface rule prone to operator-attention drift.** The hook lives in the operator's harness config, not the project repo, so it survives session boundaries and applies across every project the operator touches. Established 2026-05-06 after a same-shape attribution-footer leak occurred twice on the same repo's issue-comment surface (April + May) despite the rule being established and reinforced after the first occurrence — the third doc tweak would not have helped.
888
+ 93. **Reverse-proxy path-prefix awareness via `X-Forwarded-Prefix` — the bundle's internal `server.webroot` stays `/`, but the value templated into `gina.config.webroot` carries the proxy's mount path so client-side root-relative URLs (`/_gina/assets/routing.json`, the `gina.min.css` link injection, etc.) target the correct upstream through the proxy instead of routing to whichever bundle answered bare `/`.** Standard header used by Spring Boot, Traefik, FastAPI, ASP.NET Core, NestJS, Quarkus. Capture site: `core/server.isaac.js` proxy-detection block (sibling to the `X-Forwarded-Host` / `X-Forwarded-Proto` reads); normalisation rules — leading slash added if missing, trailing slashes stripped, empty / `"/"` dropped; stored on `process.gina.PROXY_PREFIX`. Composition site: `core/controller/controller.js` at the `set('page.environment.webroot', ...)` call (~line 513) — when `PROXY_PREFIX` is set, public webroot becomes `prefix + bundle.server.webroot` (slash-normalised), then templated into `gina.onload.min.js`. Internal disk-path resolution and asset-rewrite call sites (e.g. `controller.js:927,956`) still use the bundle's native `server.webroot` — only the browser-facing `gina.config.webroot` carries the prefix; the proxy strips the prefix when forwarding upstream so the bundle's static handlers receive native paths. Symptom shape that surfaces this gap: browser fetches `/_gina/*` URLs that nginx routes to the wrong upstream (the bundle that owns bare `/`, not the bundle that rendered the page); inspector indicator names one bundle but `routing.json` contents come from another. Server-side asset URL rewriting at `controller.js:927,956` and the `linkTo` family also write URLs intended for browser consumption — same prefix-awareness gap, deferred follow-up. The bare `/_gina/assets/routing.json` handler exists only in `server.isaac.js` with no engine-agnostic equivalent in `server.js`; bundles using the Express engine don't have this endpoint at all — independent of this slice but worth noting on the next `/_gina/*` parity sweep. Established 2026-05-06 after the symptom surfaced from a multi-bundle nginx deployment where a sub-path bundle's pages were fetching the root bundle's `routing.json`.
889
+
890
+ 92. **The leak-scan tooling that names what it scans for is itself a leak surface — a sidecar pattern resolves it.** Pattern: a repo-tracked script enforces "private tokens (operator phone, email, address, domain, legal name) must not appear in published artefacts" by hardcoding the literal token regexes in tracked source. The script ships publicly via the npm tarball + the GitHub repo, so the very tokens it scans for are visible to anyone reading the source — the tool becomes the leak. **Mitigation**: move the tokens to a gitignored sidecar (`script/.private-tokens.json`) loaded by a tracked helper (`script/_load_private_tokens.js`); the helper returns an empty array if the sidecar is absent — content-level scanning becomes a silent no-op, but path-level scanning (the local-tool path regex) still runs unchanged. Same precedent as `script/.local-sync-targets.json` (gitignored, silent-on-absent). **Sidecar shape**: `{ tokens: [{ name, pattern, flags?, allowInAttribution? }] }` where `pattern` is a regex string and `flags` is the regex-flags string (e.g. `"i"`). The `allowInAttribution: true` flag preserves the existing escape hatch for tokens that legitimately appear in authoring/contributing files matched by the script's `ATTRIBUTION_PATHS` regex (README, AUTHORS, CONTRIBUTING, package.json contributors, etc.). **Cost trade-off the maintainer should know about**: a fresh clone (or first-time fork) without the sidecar gets a downgraded prepack scan — path-level only, no content-level token check. Two follow-up shapes if that gap matters: (a) fail-closed when the sidecar is missing on stable publish; (b) ship a `.private-tokens.json.example` template as tracked documentation so the missing sidecar is at least visible in the working tree of any new clone. **Generalises to any tooling that catalogs sensitive identifiers.** Established 2026-05-06 after a public-surface leak audit surfaced the paradox in `script/check_no_local_leak.js` (the prepack hook script): five maintainer-specific tokens (private phone, email, address, domain, legal name) listed directly in tracked source. Refactored in the same session — sidecar + shared loader, both `script/check_no_local_leak.js` and `script/prepare_version.js` now go through the loader. **Follow-up (b) rejected (same day, commit `96d3b8c3`):** a `.private-tokens.json.example` template that mirrors the 5 production categories reproduces the very leak shape §92 prevents — placeholder patterns or not, it tells future readers which 5 sensitive shapes the maintainer scans for. Cleaner shape adopted instead: a one-paragraph fresh-clone-recovery nudge in the loader's JSDoc (personal backup or schema rebuild); the schema is already publicly documented in the loader's JSDoc, so no second tracked file is needed. Trim variant (single-entry trivially-generic `.example`) was also considered and skipped — still a tracked-public surface, marginal benefit over the JSDoc-only path. Follow-up (a) fail-closed-on-stable-publish landed same day (next commit after the (b)-rejected note): `script/check_no_local_leak.js` exits 1 with an actionable message when `npm_command === 'publish' && npm_config_tag === 'latest' && !fs.existsSync(sidecarPath)` — i.e. `npm publish` without `--tag alpha` AND no maintainer sidecar. Alpha publish, `npm pack`, and contributor `npm install` flows are unaffected (path-level scan still runs in all those cases). Detection probe in the gina session: `mv script/.private-tokens.json /tmp; npm_command=publish npm_config_tag=latest node script/check_no_local_leak.js; mv /tmp/.private-tokens.json script/` returns exit=1 with the gate's message.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gina",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "Node.js MVC framework with built-in HTTP/2, multi-bundle architecture, and scope-based data isolation — no Express dependency",
5
5
  "keywords": [
6
6
  "nodejs",
@@ -56,7 +56,7 @@
56
56
  "gina-container": "bin/gina-container",
57
57
  "gina-init": "bin/gina-init"
58
58
  },
59
- "main": "./framework/v0.3.9/core/gna",
59
+ "main": "./framework/v0.3.10/core/gna",
60
60
  "types": "./types/index.d.ts",
61
61
  "typesVersions": {
62
62
  "*": {
@@ -85,6 +85,7 @@
85
85
  "devDependencies": {
86
86
  "csso-cli": "4.0.2",
87
87
  "engine.io-client": "^6.6.3",
88
+ "jsdom": "^29.1.1",
88
89
  "requirejs": "^2.3.7",
89
90
  "sass": "1.99.0"
90
91
  }
@@ -20,7 +20,10 @@
20
20
 
21
21
  set -e
22
22
 
23
- BLOCKED_PATTERNS='freelancer\.app|\.local$|rhinostone\.com|freelancer-app\.fr'
23
+ # Default catches a common dev-environment leak (the .local hostname suffix used
24
+ # on macOS). Add patterns matching your private domain, company hostname, or
25
+ # any other identity that shouldn't end up in commits — pipe-separated.
26
+ BLOCKED_PATTERNS='\.local$'
24
27
 
25
28
  email="$(git config user.email || true)"
26
29
 
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * This file is part of the gina package.
5
+ * Copyright (c) 2009-2026 Rhinostone <contact@gina.io>
6
+ *
7
+ * For the full copyright and license information, please view the LICENSE
8
+ * file that was distributed with this source code.
9
+ */
10
+
11
+ /**
12
+ * Shared loader for the maintainer-local private-token sidecar used by
13
+ * `script/check_no_local_leak.js` (prepack hook) and the leak-scan
14
+ * function inside `script/prepare_version.js` (prepare hook).
15
+ *
16
+ * Reads `script/.private-tokens.json` (gitignored, never published to npm)
17
+ * and returns a list of { name, pattern, allowIn? } entries the calling
18
+ * script feeds into its content scanner. Sidecar format:
19
+ *
20
+ * {
21
+ * "tokens": [
22
+ * { "name": "...", "pattern": "<regex string>", "flags": "i",
23
+ * "allowInAttribution": true|false }
24
+ * ]
25
+ * }
26
+ *
27
+ * The caller passes its `attributionPathPattern` (the regex matching
28
+ * authoring/contributing files where a token like a legal name is
29
+ * allowed). Entries with `allowInAttribution: true` get that pattern
30
+ * attached as `allowIn`, mirroring the original hardcoded shape.
31
+ *
32
+ * If the sidecar is absent or malformed, returns an empty array. The
33
+ * caller's path-level scan (CLAUDE.md / .claude*) still runs unchanged;
34
+ * only content-level scanning is silenced. This matches the precedent
35
+ * of `script/.local-sync-targets.json` (silent no-op when absent).
36
+ *
37
+ * Fresh-clone recovery (new machine, restored backup): recreate the
38
+ * sidecar from a personal backup outside the repo, or rebuild it
39
+ * from the schema above before running stable publish.
40
+ *
41
+ * @param {RegExp} attributionPathPattern
42
+ * @returns {Array<{name: string, pattern: RegExp, allowIn?: RegExp}>}
43
+ */
44
+
45
+ var fs = require('fs');
46
+ var path = require('path');
47
+
48
+ module.exports = function loadPrivateTokens(attributionPathPattern) {
49
+ var sidecar = path.join(__dirname, '.private-tokens.json');
50
+ if (!fs.existsSync(sidecar)) {
51
+ return [];
52
+ }
53
+
54
+ var raw;
55
+ try {
56
+ raw = JSON.parse(fs.readFileSync(sidecar, 'utf8'));
57
+ } catch (err) {
58
+ console.error('[private-tokens] WARNING: failed to parse ' + sidecar + ': ' + (err.message || err));
59
+ return [];
60
+ }
61
+
62
+ var entries = (raw && raw.tokens) || [];
63
+ var out = [];
64
+ for (var i = 0; i < entries.length; i++) {
65
+ var t = entries[i];
66
+ if (!t || typeof t.name !== 'string' || typeof t.pattern !== 'string') {
67
+ continue;
68
+ }
69
+ var flags = typeof t.flags === 'string' ? t.flags : '';
70
+ var entry = { name: t.name, pattern: new RegExp(t.pattern, flags) };
71
+ if (t.allowInAttribution && attributionPathPattern) {
72
+ entry.allowIn = attributionPathPattern;
73
+ }
74
+ out.push(entry);
75
+ }
76
+ return out;
77
+ };