@runeditrun/rer 0.0.0-stage → 0.1.0

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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/bin/rer.mjs +3 -0
  4. package/cli/README.md +243 -0
  5. package/cli/adapter.mjs +10 -0
  6. package/cli/ai-local.mjs +65 -0
  7. package/cli/archive-path.mjs +52 -0
  8. package/cli/archive-vite.mjs +55 -0
  9. package/cli/archive.mjs +8 -0
  10. package/cli/auth-copy.mjs +80 -0
  11. package/cli/bounded.mjs +70 -0
  12. package/cli/cache-doctor.mjs +63 -0
  13. package/cli/container-transfer.mjs +35 -0
  14. package/cli/container.mjs +62 -0
  15. package/cli/contract.mjs +25 -0
  16. package/cli/dependencies.mjs +45 -0
  17. package/cli/dependency-approval.mjs +33 -0
  18. package/cli/dependency-doctor.mjs +34 -0
  19. package/cli/dependency-input.mjs +43 -0
  20. package/cli/deploy-config.mjs +118 -0
  21. package/cli/deploy.mjs +37 -0
  22. package/cli/dev-identity.mjs +80 -0
  23. package/cli/errors.mjs +14 -0
  24. package/cli/export.mjs +42 -0
  25. package/cli/font-cache.mjs +29 -0
  26. package/cli/hosted-output.mjs +110 -0
  27. package/cli/independent.mjs +29 -0
  28. package/cli/inspect.mjs +174 -0
  29. package/cli/jsonc.mjs +111 -0
  30. package/cli/lifecycle.mjs +148 -0
  31. package/cli/local.mjs +100 -0
  32. package/cli/main.mjs +57 -0
  33. package/cli/managed-server.mjs +143 -0
  34. package/cli/node-runtime-cache.mjs +68 -0
  35. package/cli/node-runtime-discovery.mjs +56 -0
  36. package/cli/node-runtime-download.mjs +59 -0
  37. package/cli/node-runtime.mjs +50 -0
  38. package/cli/output.mjs +22 -0
  39. package/cli/parse.mjs +77 -0
  40. package/cli/port.mjs +52 -0
  41. package/cli/portability-worker.mjs +56 -0
  42. package/cli/portability.mjs +66 -0
  43. package/cli/preview-cache.mjs +99 -0
  44. package/cli/project.mjs +48 -0
  45. package/cli/release.mjs +24 -0
  46. package/cli/runtime-front-door.mjs +46 -0
  47. package/cli/scheduled.mjs +81 -0
  48. package/cli/server-activity.mjs +12 -0
  49. package/cli/server-proxy.mjs +93 -0
  50. package/cli/server-registry.mjs +177 -0
  51. package/cli/server-resources.mjs +22 -0
  52. package/cli/settings.mjs +15 -0
  53. package/cli/setup-answers.mjs +29 -0
  54. package/cli/setup-input.mjs +113 -0
  55. package/cli/shared.mjs +7 -0
  56. package/cli/transfers.mjs +33 -0
  57. package/cli/version.mjs +36 -0
  58. package/composition/git-source.mjs +111 -0
  59. package/edition-authoring/files.mjs +81 -0
  60. package/edition-authoring/manifest.mjs +65 -0
  61. package/global/README.md +155 -0
  62. package/global/checkout.mjs +72 -0
  63. package/global/cli/command.mjs +13 -0
  64. package/global/cli/install.mjs +75 -0
  65. package/global/cli/layout.mjs +45 -0
  66. package/global/cli/status.mjs +54 -0
  67. package/global/cli/uninstall.mjs +36 -0
  68. package/global/delegate.mjs +13 -0
  69. package/global/launch/capacity.mjs +72 -0
  70. package/global/launch/dev.mjs +129 -0
  71. package/global/launch/facts.mjs +93 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +61 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +97 -0
  77. package/global/launch/steps.mjs +81 -0
  78. package/global/launch/worker.mjs +43 -0
  79. package/global/list.mjs +59 -0
  80. package/global/main.mjs +53 -0
  81. package/global/package-info.mjs +38 -0
  82. package/global/registry.mjs +24 -0
  83. package/global/rer.mjs +29 -0
  84. package/global/update/archive.mjs +89 -0
  85. package/global/update/command.mjs +90 -0
  86. package/global/update/latest.mjs +6 -0
  87. package/global/update/notice.mjs +58 -0
  88. package/global/update/registry.mjs +47 -0
  89. package/global/version.mjs +29 -0
  90. package/install.sh +144 -0
  91. package/modules/ai/LICENSE +21 -0
  92. package/modules/ai/README.md +90 -0
  93. package/modules/ai/client.mjs +71 -0
  94. package/modules/ai/config.mjs +39 -0
  95. package/modules/ai/dev/index.d.mts +6 -0
  96. package/modules/ai/dev/index.mjs +64 -0
  97. package/modules/ai/dev/outbox.mjs +33 -0
  98. package/modules/ai/errors.mjs +28 -0
  99. package/modules/ai/fake/index.d.mts +3 -0
  100. package/modules/ai/fake/index.mjs +82 -0
  101. package/modules/ai/index.d.mts +10 -0
  102. package/modules/ai/index.mjs +3 -0
  103. package/modules/ai/package.json +6 -0
  104. package/modules/container/README.md +156 -0
  105. package/modules/container/adapters/d1.mjs +34 -0
  106. package/modules/container/adapters/egress.mjs +40 -0
  107. package/modules/container/adapters/jobs.mjs +47 -0
  108. package/modules/container/adapters/objects.mjs +84 -0
  109. package/modules/container/adapters/retained.mjs +14 -0
  110. package/modules/container/adapters/sqlite-error.mjs +7 -0
  111. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  112. package/modules/container/assets.mjs +14 -0
  113. package/modules/container/bindings.mjs +1 -0
  114. package/modules/container/boundary.mjs +38 -0
  115. package/modules/container/build.mjs +65 -0
  116. package/modules/container/deploy-input.mjs +17 -0
  117. package/modules/container/entrypoint.sh +5 -0
  118. package/modules/container/host.mjs +43 -0
  119. package/modules/container/ingress.mjs +23 -0
  120. package/modules/container/lock.mjs +11 -0
  121. package/modules/container/migrations.mjs +16 -0
  122. package/modules/container/node-engine.mjs +39 -0
  123. package/modules/container/portability-schema.mjs +14 -0
  124. package/modules/container/remote/deploy.py +76 -0
  125. package/modules/container/remote/files.py +275 -0
  126. package/modules/container/remote/resources.py +157 -0
  127. package/modules/container/runner.mjs +10 -0
  128. package/modules/container/ssh-plan.mjs +44 -0
  129. package/modules/container/ssh.mjs +63 -0
  130. package/modules/container/transfer.mjs +27 -0
  131. package/modules/container/worker-engine.mjs +32 -0
  132. package/modules/dependencies/README.md +211 -0
  133. package/modules/dependencies/approval.mjs +51 -0
  134. package/modules/dependencies/auth.mjs +40 -0
  135. package/modules/dependencies/authority.mjs +14 -0
  136. package/modules/dependencies/crypto.mjs +13 -0
  137. package/modules/dependencies/index.d.mts +23 -0
  138. package/modules/dependencies/index.mjs +113 -0
  139. package/modules/dependencies/logout.mjs +42 -0
  140. package/modules/dependencies/native.mjs +9 -0
  141. package/modules/dependencies/protocol.mjs +23 -0
  142. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  143. package/modules/dependencies/schema/dependencies.sql +15 -0
  144. package/modules/dependencies/store.mjs +19 -0
  145. package/modules/deploy-kit/README.md +308 -0
  146. package/modules/deploy-kit/artifact.mjs +47 -0
  147. package/modules/deploy-kit/build-modules.mjs +21 -0
  148. package/modules/deploy-kit/check.mjs +13 -0
  149. package/modules/deploy-kit/cli.mjs +50 -0
  150. package/modules/deploy-kit/container-config.mjs +8 -0
  151. package/modules/deploy-kit/default-stage.mjs +14 -0
  152. package/modules/deploy-kit/deploy.mjs +80 -0
  153. package/modules/deploy-kit/health-request.mjs +86 -0
  154. package/modules/deploy-kit/health.mjs +99 -0
  155. package/modules/deploy-kit/index.mjs +12 -0
  156. package/modules/deploy-kit/local-only.mjs +10 -0
  157. package/modules/deploy-kit/migrations.mjs +48 -0
  158. package/modules/deploy-kit/owner-cli.mjs +63 -0
  159. package/modules/deploy-kit/owner.mjs +297 -0
  160. package/modules/deploy-kit/prepare.mjs +62 -0
  161. package/modules/deploy-kit/provider.mjs +99 -0
  162. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  163. package/modules/deploy-kit/receipt.mjs +16 -0
  164. package/modules/deploy-kit/render.mjs +47 -0
  165. package/modules/deploy-kit/resources.mjs +106 -0
  166. package/modules/deploy-kit/service-grants.mjs +25 -0
  167. package/modules/deploy-kit/sql-safety.mjs +94 -0
  168. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  169. package/modules/deploy-kit/teardown.mjs +100 -0
  170. package/modules/deploy-kit/templates.mjs +31 -0
  171. package/modules/deploy-kit/validation.mjs +58 -0
  172. package/modules/deploy-kit/worker.mjs +105 -0
  173. package/modules/erasure/CONTRACT.md +90 -0
  174. package/modules/erasure/README.md +221 -0
  175. package/modules/erasure/audit.mjs +23 -0
  176. package/modules/erasure/blobs.mjs +45 -0
  177. package/modules/erasure/cascade.mjs +91 -0
  178. package/modules/erasure/completion.mjs +62 -0
  179. package/modules/erasure/declaration.mjs +51 -0
  180. package/modules/erasure/fence.mjs +19 -0
  181. package/modules/erasure/index.d.mts +62 -0
  182. package/modules/erasure/index.mjs +85 -0
  183. package/modules/erasure/order.mjs +56 -0
  184. package/modules/erasure/plan.mjs +121 -0
  185. package/modules/erasure/rate.mjs +57 -0
  186. package/modules/erasure/receipt.mjs +32 -0
  187. package/modules/erasure/root.mjs +21 -0
  188. package/modules/erasure/schema/erasure-rates.sql +5 -0
  189. package/modules/erasure/schema/erasure.sql +27 -0
  190. package/modules/erasure/scope.mjs +29 -0
  191. package/modules/erasure/sql.mjs +23 -0
  192. package/modules/erasure/verification.mjs +69 -0
  193. package/modules/integrations/core/CONTRACT.md +50 -0
  194. package/modules/integrations/core/README.md +27 -0
  195. package/modules/integrations/core/crypto.ts +19 -0
  196. package/modules/integrations/core/database.ts +21 -0
  197. package/modules/integrations/core/delivery.ts +97 -0
  198. package/modules/integrations/core/destination.ts +13 -0
  199. package/modules/integrations/core/index.ts +7 -0
  200. package/modules/integrations/core/node-transport.ts +72 -0
  201. package/modules/integrations/core/public-address.ts +57 -0
  202. package/modules/integrations/core/repository.ts +74 -0
  203. package/modules/integrations/core/schema.sql +36 -0
  204. package/modules/integrations/core/sqlite.ts +20 -0
  205. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  206. package/modules/integrations/core/tokens.ts +71 -0
  207. package/modules/integrations/core/types.ts +52 -0
  208. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  209. package/modules/integrations/core/webhook.ts +73 -0
  210. package/modules/job-queue/README.md +173 -0
  211. package/modules/job-queue/cli.mjs +64 -0
  212. package/modules/job-queue/command.mjs +64 -0
  213. package/modules/job-queue/config.mjs +48 -0
  214. package/modules/job-queue/identity.mjs +48 -0
  215. package/modules/job-queue/index.mjs +135 -0
  216. package/modules/job-queue/membership.mjs +30 -0
  217. package/modules/job-queue/module.json +10 -0
  218. package/modules/job-queue/policy.mjs +29 -0
  219. package/modules/job-queue/processes.mjs +80 -0
  220. package/modules/job-queue/resources.mjs +37 -0
  221. package/modules/job-queue/storage.mjs +64 -0
  222. package/modules/job-queue/supervisor.mjs +30 -0
  223. package/modules/job-queue/waiting.mjs +16 -0
  224. package/modules/mail/LICENSE +21 -0
  225. package/modules/mail/README.md +336 -0
  226. package/modules/mail/doctor.mjs +81 -0
  227. package/modules/mail/index.mjs +8 -0
  228. package/modules/mail/live-proof/README.md +113 -0
  229. package/modules/mail/live-proof/run.mjs +55 -0
  230. package/modules/mail/package.json +19 -0
  231. package/modules/mail/pnpm-lock.yaml +537 -0
  232. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  233. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  234. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  235. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  236. package/modules/mail/src/adapters/messaging/README.md +92 -0
  237. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  238. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  239. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  240. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  241. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  242. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  243. package/modules/mail/src/adapters/resend/README.md +120 -0
  244. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  245. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  246. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  247. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  248. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  249. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  250. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  251. package/modules/mail/src/core/errors.mjs +9 -0
  252. package/modules/mail/src/core/transport.mjs +33 -0
  253. package/modules/mail/src/core/validation.mjs +172 -0
  254. package/modules/mail/src/development/index.d.mts +37 -0
  255. package/modules/mail/src/development/index.mjs +35 -0
  256. package/modules/mail/src/development/outbox.mjs +78 -0
  257. package/modules/mail/src/development/schema.sql +9 -0
  258. package/modules/mail/src/development/viewer-html.mjs +42 -0
  259. package/modules/mail/src/development/viewer.mjs +65 -0
  260. package/modules/mail/src/doctor/README.md +107 -0
  261. package/modules/mail/src/doctor/common.mjs +64 -0
  262. package/modules/mail/src/doctor/dns.mjs +102 -0
  263. package/modules/mail/src/doctor/index.mjs +28 -0
  264. package/modules/mail/src/doctor/probe.mjs +66 -0
  265. package/modules/mail/src/doctor/receive.mjs +81 -0
  266. package/modules/mail/src/doctor/records.mjs +45 -0
  267. package/modules/mail/src/events/README.md +268 -0
  268. package/modules/mail/src/events/d1-schema.sql +25 -0
  269. package/modules/mail/src/events/d1-storage.mjs +76 -0
  270. package/modules/mail/src/events/index.mjs +3 -0
  271. package/modules/mail/src/events/intake.mjs +201 -0
  272. package/modules/mail/src/events/storage.mjs +52 -0
  273. package/modules/mail/src/events/verify.mjs +92 -0
  274. package/modules/mail/src/inbound/README.md +137 -0
  275. package/modules/mail/src/inbound/bytes.mjs +91 -0
  276. package/modules/mail/src/inbound/headers.mjs +143 -0
  277. package/modules/mail/src/inbound/index.mjs +29 -0
  278. package/modules/mail/src/inbound/mime.mjs +36 -0
  279. package/modules/mail/src/inbound/multipart.mjs +27 -0
  280. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  281. package/modules/mail/src/inbound/resend.mjs +54 -0
  282. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  283. package/modules/mail/src/sender.mjs +33 -0
  284. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  285. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  286. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  287. package/modules/operator-auth/README.md +599 -0
  288. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  289. package/modules/operator-auth/accounts/admin.mjs +159 -0
  290. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  291. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  292. package/modules/operator-auth/accounts/index.d.mts +9 -0
  293. package/modules/operator-auth/accounts/index.mjs +9 -0
  294. package/modules/operator-auth/accounts/native.d.mts +38 -0
  295. package/modules/operator-auth/accounts/native.mjs +171 -0
  296. package/modules/operator-auth/accounts/oa.mjs +90 -0
  297. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  298. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  299. package/modules/operator-auth/accounts/roles.mjs +26 -0
  300. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  301. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  302. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  303. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  304. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  305. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  306. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  307. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  308. package/modules/operator-auth/accounts/store.mjs +246 -0
  309. package/modules/operator-auth/branding.mjs +66 -0
  310. package/modules/operator-auth/config.mjs +35 -0
  311. package/modules/operator-auth/conformance/README.md +81 -0
  312. package/modules/operator-auth/conformance/index.d.mts +43 -0
  313. package/modules/operator-auth/conformance/index.mjs +300 -0
  314. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  315. package/modules/operator-auth/crypto.mjs +17 -0
  316. package/modules/operator-auth/dev/badge.mjs +62 -0
  317. package/modules/operator-auth/dev/index.d.mts +14 -0
  318. package/modules/operator-auth/dev/index.mjs +137 -0
  319. package/modules/operator-auth/dev/personas.mjs +17 -0
  320. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  321. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  322. package/modules/operator-auth/dev-admission.d.mts +4 -0
  323. package/modules/operator-auth/dev-admission.mjs +10 -0
  324. package/modules/operator-auth/dev-gate.mjs +86 -0
  325. package/modules/operator-auth/docs/README.md +78 -0
  326. package/modules/operator-auth/docs/native-accounts.md +313 -0
  327. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  328. package/modules/operator-auth/docs/vite-spa.md +144 -0
  329. package/modules/operator-auth/errors.mjs +9 -0
  330. package/modules/operator-auth/http.mjs +42 -0
  331. package/modules/operator-auth/identity/admission.mjs +105 -0
  332. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  333. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  334. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  335. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  336. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  337. package/modules/operator-auth/identity/index.d.mts +7 -0
  338. package/modules/operator-auth/identity/index.mjs +5 -0
  339. package/modules/operator-auth/identity/oidc/README.md +171 -0
  340. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  341. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  342. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  343. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  344. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  345. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  346. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  347. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  348. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  349. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  350. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  351. package/modules/operator-auth/identity/principal.mjs +28 -0
  352. package/modules/operator-auth/index.d.mts +223 -0
  353. package/modules/operator-auth/index.mjs +11 -0
  354. package/modules/operator-auth/input.mjs +55 -0
  355. package/modules/operator-auth/local-cli.mjs +16 -0
  356. package/modules/operator-auth/page.mjs +34 -0
  357. package/modules/operator-auth/policy.mjs +20 -0
  358. package/modules/operator-auth/routes.mjs +91 -0
  359. package/modules/operator-auth/service.mjs +152 -0
  360. package/modules/operator-auth/signin/index.mjs +67 -0
  361. package/modules/owner-claim/README.md +137 -0
  362. package/modules/owner-claim/idp.mjs +40 -0
  363. package/modules/owner-claim/index.d.mts +27 -0
  364. package/modules/owner-claim/index.mjs +39 -0
  365. package/modules/owner-claim/page.mjs +11 -0
  366. package/modules/owner-claim/schema/idp.sql +4 -0
  367. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  368. package/modules/owner-claim/transaction.mjs +87 -0
  369. package/modules/owner-claim/verify.mjs +80 -0
  370. package/modules/portability/README.md +444 -0
  371. package/modules/portability/bridge.mjs +102 -0
  372. package/modules/portability/catalogue.mjs +118 -0
  373. package/modules/portability/cli-options.mjs +38 -0
  374. package/modules/portability/cli.mjs +56 -0
  375. package/modules/portability/copies.mjs +68 -0
  376. package/modules/portability/dev-data.mjs +32 -0
  377. package/modules/portability/diff.mjs +33 -0
  378. package/modules/portability/export.mjs +129 -0
  379. package/modules/portability/files.mjs +65 -0
  380. package/modules/portability/format.mjs +122 -0
  381. package/modules/portability/generated.mjs +60 -0
  382. package/modules/portability/hash.mjs +79 -0
  383. package/modules/portability/http.mjs +42 -0
  384. package/modules/portability/import.mjs +185 -0
  385. package/modules/portability/index.d.mts +58 -0
  386. package/modules/portability/index.mjs +22 -0
  387. package/modules/portability/journal.mjs +49 -0
  388. package/modules/portability/legacy-files.mjs +43 -0
  389. package/modules/portability/legacy-safety.mjs +27 -0
  390. package/modules/portability/lines.mjs +36 -0
  391. package/modules/portability/migration-history.mjs +18 -0
  392. package/modules/portability/migration-statements.mjs +23 -0
  393. package/modules/portability/payloads.mjs +52 -0
  394. package/modules/portability/provider-data.mjs +25 -0
  395. package/modules/portability/restore-safety.mjs +30 -0
  396. package/modules/portability/schema/portability.sql +25 -0
  397. package/modules/portability/storage.mjs +49 -0
  398. package/modules/release/README.md +212 -0
  399. package/modules/release/catalogue.mjs +79 -0
  400. package/modules/release/export.mjs +51 -0
  401. package/modules/release/produce.mjs +136 -0
  402. package/modules/release/public-child-transport-state.cjs +29 -0
  403. package/modules/release/public-child.mjs +71 -0
  404. package/modules/release/public-git.mjs +21 -0
  405. package/modules/release/publication-scan.mjs +22 -0
  406. package/modules/release/publish-handoff.mjs +37 -0
  407. package/modules/release/source.mjs +71 -0
  408. package/modules/release/test-summary.mjs +50 -0
  409. package/onboarding/README.md +163 -0
  410. package/onboarding/activity.mjs +31 -0
  411. package/onboarding/adapter-auth.mjs +88 -0
  412. package/onboarding/adapter-hosting.mjs +35 -0
  413. package/onboarding/adapter-labels.mjs +13 -0
  414. package/onboarding/adapter-settings.mjs +129 -0
  415. package/onboarding/adapter.mjs +238 -0
  416. package/onboarding/agent-handoff.mjs +9 -0
  417. package/onboarding/app-processes.mjs +21 -0
  418. package/onboarding/archive-output.d.mts +3 -0
  419. package/onboarding/archive-output.mjs +54 -0
  420. package/onboarding/command-custody.mjs +24 -0
  421. package/onboarding/command-recovery.mjs +8 -0
  422. package/onboarding/commands.mjs +166 -0
  423. package/onboarding/dependency-approval.mjs +36 -0
  424. package/onboarding/dev-identity.mjs +23 -0
  425. package/onboarding/email.mjs +24 -0
  426. package/onboarding/failures.mjs +42 -0
  427. package/onboarding/input-limits.mjs +7 -0
  428. package/onboarding/installer-inputs.mjs +24 -0
  429. package/onboarding/launcher.mjs +34 -0
  430. package/onboarding/local/build-files.mjs +64 -0
  431. package/onboarding/local/build.mjs +49 -0
  432. package/onboarding/local/environment.mjs +30 -0
  433. package/onboarding/local/files.mjs +35 -0
  434. package/onboarding/local/operation.mjs +166 -0
  435. package/onboarding/local/owner.mjs +34 -0
  436. package/onboarding/local/process-table.mjs +114 -0
  437. package/onboarding/local/recovery.mjs +79 -0
  438. package/onboarding/local/state.mjs +31 -0
  439. package/onboarding/metadata.mjs +7 -0
  440. package/onboarding/operator-install.mjs +45 -0
  441. package/onboarding/operator-installer.mjs +39 -0
  442. package/onboarding/operator-prerequisites.mjs +27 -0
  443. package/onboarding/package.json +6 -0
  444. package/onboarding/prefill.mjs +16 -0
  445. package/onboarding/private-files.mjs +97 -0
  446. package/onboarding/private.mjs +35 -0
  447. package/onboarding/process-cleanup.mjs +134 -0
  448. package/onboarding/process-diagnostics.mjs +91 -0
  449. package/onboarding/process-scope.mjs +118 -0
  450. package/onboarding/process-table.mjs +118 -0
  451. package/onboarding/process-tree.mjs +36 -0
  452. package/onboarding/production-modules.mjs +22 -0
  453. package/onboarding/production-options.mjs +40 -0
  454. package/onboarding/production-plan.mjs +8 -0
  455. package/onboarding/production-runtime.mjs +30 -0
  456. package/onboarding/project-vite.mjs +14 -0
  457. package/onboarding/providers.mjs +289 -0
  458. package/onboarding/queue.mjs +53 -0
  459. package/onboarding/queued-entry.mjs +34 -0
  460. package/onboarding/readiness.mjs +59 -0
  461. package/onboarding/redaction-jwt.mjs +32 -0
  462. package/onboarding/redaction.mjs +298 -0
  463. package/onboarding/schema/SETUP.schema.json +514 -0
  464. package/onboarding/schema/dependencies.mjs +35 -0
  465. package/onboarding/schema/json-schema.mjs +202 -0
  466. package/onboarding/schema/setup-validation.mjs +325 -0
  467. package/onboarding/secrets.mjs +21 -0
  468. package/onboarding/server.mjs +188 -0
  469. package/onboarding/setup-manifest.mjs +116 -0
  470. package/onboarding/state.mjs +46 -0
  471. package/onboarding/theme-files.mjs +37 -0
  472. package/onboarding/theme.mjs +118 -0
  473. package/onboarding/ui/activity.js +98 -0
  474. package/onboarding/ui/address-client.mjs +33 -0
  475. package/onboarding/ui/app.js +377 -0
  476. package/onboarding/ui/index.html +1 -0
  477. package/onboarding/ui/model-core.mjs +127 -0
  478. package/onboarding/ui/model.mjs +5 -0
  479. package/onboarding/ui/style.css +63 -0
  480. package/onboarding/ui/theme.css +37 -0
  481. package/onboarding/ui/workspace-art.js +30 -0
  482. package/onboarding/wrangler-auth.mjs +43 -0
  483. package/operations/README.md +246 -0
  484. package/operations/operations.mjs +168 -0
  485. package/operations/project-operations.mjs +157 -0
  486. package/package.json +11 -4
  487. package/scripts/publish-check/README.md +72 -0
  488. package/scripts/publish-check/checksum_maps.py +52 -0
  489. package/scripts/publish-check/cli.py +34 -0
  490. package/scripts/publish-check/command_arguments.py +51 -0
  491. package/scripts/publish-check/command_shell_quotes.py +41 -0
  492. package/scripts/publish-check/command_source_quotes.py +120 -0
  493. package/scripts/publish-check/content_identities.py +250 -0
  494. package/scripts/publish-check/copy_identities.py +41 -0
  495. package/scripts/publish-check/der.py +61 -0
  496. package/scripts/publish-check/destination.py +117 -0
  497. package/scripts/publish-check/env_values.py +74 -0
  498. package/scripts/publish-check/font_licence.py +23 -0
  499. package/scripts/publish-check/git_history.py +115 -0
  500. package/scripts/publish-check/github_refs.py +217 -0
  501. package/scripts/publish-check/gitleaks.py +72 -0
  502. package/scripts/publish-check/gitleaks.toml +40 -0
  503. package/scripts/publish-check/history_scan.py +176 -0
  504. package/scripts/publish-check/hit_location.py +18 -0
  505. package/scripts/publish-check/hygiene_config.py +42 -0
  506. package/scripts/publish-check/hygiene_text.py +11 -0
  507. package/scripts/publish-check/identity-recipe.mjs +54 -0
  508. package/scripts/publish-check/json_identity.py +79 -0
  509. package/scripts/publish-check/license-exceptions.json +11 -0
  510. package/scripts/publish-check/licenses.py +49 -0
  511. package/scripts/publish-check/policy.py +18 -0
  512. package/scripts/publish-check/project_identity.py +89 -0
  513. package/scripts/publish-check/provenance.py +91 -0
  514. package/scripts/publish-check/provenance_pins.py +33 -0
  515. package/scripts/publish-check/public_base.py +63 -0
  516. package/scripts/publish-check/public_hygiene.py +80 -0
  517. package/scripts/publish-check/publication-config.json +12 -0
  518. package/scripts/publish-check/publication_config.py +34 -0
  519. package/scripts/publish-check/report.py +52 -0
  520. package/scripts/publish-check/repository_commands.py +75 -0
  521. package/scripts/publish-check/repository_intent.py +52 -0
  522. package/scripts/publish-check/reviewed.py +41 -0
  523. package/scripts/publish-check/source_hashes.mjs +6 -0
  524. package/scripts/publish-check/tree_scan.py +88 -0
  525. package/scripts/publish-check/trust_bundle.py +87 -0
  526. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,23 @@
1
+ """The copyright notice at the top of a SIL Open Font License text."""
2
+ import re
3
+
4
+ # The licence text itself, not a path or a mention of the licence: its title
5
+ # line followed by sections only the full text carries.
6
+ TITLE = re.compile(r'(?im)^[ \t]*SIL OPEN FONT LICENSE[ \t]+Version[ \t]+1\.1\b.*$')
7
+ BODY = ('PERMISSION & CONDITIONS', 'Reserved Font Name', 'Font Software')
8
+ # An attribution line names a year after the word: "Copyright (c) 2014 Holder".
9
+ # Other prose that starts with Copyright, such as a contact line, stays scanned.
10
+ NOTICE = re.compile(r'(?im)^[ \t]*Copyright[ \t]+(?:(?:\(c\)|©)[ \t]*)?\d{4}\b.*$')
11
+
12
+
13
+ def without_required_notices(text):
14
+ """Drop the notice lines above the licence title of a full OFL text.
15
+
16
+ The licence obliges a redistributor to keep these lines, so the address in
17
+ one is required attribution. Anything else in the file, and the same
18
+ notice in a file that is not an OFL text, stays visible.
19
+ """
20
+ title = TITLE.search(text)
21
+ if not title or not all(part.lower() in text[title.end():].lower() for part in BODY):
22
+ return text
23
+ return NOTICE.sub('', text[:title.start()]) + text[title.start():]
@@ -0,0 +1,115 @@
1
+ """Repository plumbing delegated to the installed Git, never decoded in Python."""
2
+ import os
3
+ from pathlib import Path
4
+ import subprocess
5
+ from public_base import parse, display, PublicBaseError
6
+
7
+
8
+ class InspectionError(Exception):
9
+ pass
10
+
11
+
12
+ def git_environment():
13
+ env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
14
+ env.update(GIT_TERMINAL_PROMPT='0', GIT_NO_REPLACE_OBJECTS='1')
15
+ return env
16
+
17
+
18
+ class History:
19
+ def __init__(self, repo, ref='HEAD', public_base=None):
20
+ self.repo = str(Path(repo).resolve())
21
+ self.env = git_environment()
22
+ self.public_commit = self.public_label = None
23
+ self.reader = None
24
+ self.commit = self.run('rev-parse', '--verify', '--end-of-options', ref + '^{commit}').decode().strip()
25
+ if self.run('rev-parse', '--is-shallow-repository').strip() != b'false':
26
+ raise InspectionError('history.shallow')
27
+ if public_base is not None:
28
+ url, branch = parse(public_base, self.repo)
29
+ try:
30
+ self.run('fetch', '--quiet', '--no-tags', '--no-recurse-submodules', '--no-write-fetch-head', '--',
31
+ url, 'refs/heads/' + branch + ':refs/publish-check/public/base')
32
+ self.public_commit = self.run('rev-parse', 'refs/publish-check/public/base^{commit}').decode().strip()
33
+ self.public_label = display(url) + '#' + branch
34
+ except InspectionError:
35
+ raise PublicBaseError('public-base.fetch-failed') from None
36
+ self.revisions = [self.commit, self.public_commit] if public_base else ['--all', self.commit]
37
+ self.commits = self.run('rev-list', *self.revisions).decode().splitlines()
38
+ self.public_commits = set(self.run('rev-list', self.public_commit).decode().splitlines()) if self.public_commit else set()
39
+ self.trees = {}
40
+ self.tree_ids = dict(line.split() for line in self.run('log', '--format=%H %T', *self.revisions).decode().splitlines())
41
+ self.reader = subprocess.Popen(['git', '-C', self.repo, 'cat-file', '--batch'],
42
+ env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
43
+
44
+ def run(self, *args):
45
+ try:
46
+ result = subprocess.run(['git', '-C', self.repo, '-c', 'core.quotePath=false', *args],
47
+ env=self.env, capture_output=True, timeout=120)
48
+ if result.returncode:
49
+ raise InspectionError('history.git-failed')
50
+ return result.stdout
51
+ except (OSError, subprocess.TimeoutExpired):
52
+ raise InspectionError('history.git-unavailable') from None
53
+
54
+ def commit_tree(self, commit):
55
+ known = getattr(self, 'tree_ids', {}).get(commit)
56
+ return known.encode() if known else self.run('rev-parse', commit + '^{tree}').strip()
57
+
58
+ def entries(self, commit, recursive=True):
59
+ tree = self.commit_tree(commit).decode()
60
+ key = (tree, recursive)
61
+ if key not in self.trees:
62
+ args = ['ls-tree', '-z', '-l'] + (['-r'] if recursive else []) + [tree]
63
+ entries = []
64
+ for record in self.run(*args).split(b'\0'):
65
+ if not record:
66
+ continue
67
+ metadata, path = record.split(b'\t', 1)
68
+ mode, kind, oid, size = metadata.split()
69
+ entries.append(dict(path=os.fsdecode(path), mode=mode.decode(), kind=kind.decode(),
70
+ oid=oid.decode(), size=int(size) if size != b'-' else 0))
71
+ self.trees[key] = entries
72
+ return self.trees[key]
73
+
74
+ def root_entries(self, commit):
75
+ return self.entries(commit, False)
76
+
77
+ def blob(self, oid):
78
+ if not oid or any(c not in '0123456789abcdef' for c in oid):
79
+ raise InspectionError('history.invalid-object')
80
+ self.reader.stdin.write(oid.encode() + b'\n')
81
+ self.reader.stdin.flush()
82
+ header = self.reader.stdout.readline().split()
83
+ if len(header) != 3 or header[1] != b'blob':
84
+ raise InspectionError('history.missing-blob')
85
+ size = int(header[2])
86
+ data = self.reader.stdout.read(size)
87
+ if len(data) != size or self.reader.stdout.read(1) != b'\n':
88
+ raise InspectionError('history.missing-blob')
89
+ return data
90
+
91
+ def selected(self, path):
92
+ entry = next((e for e in self.entries(self.commit) if e['path'] == path), None)
93
+ return self.blob(entry['oid']) if entry and entry['kind'] == 'blob' else None
94
+
95
+ def messages(self):
96
+ records = self.run('log', '-z', '--format=%H%x00%B', *self.revisions).split(b'\0')
97
+ for index in range(0, len(records) - 1, 2):
98
+ if records[index]:
99
+ yield '@commit-message', records[index].decode().strip(), records[index + 1]
100
+ for record in self.run('for-each-ref', '--format=%(objecttype) %(objectname)', 'refs/tags').splitlines():
101
+ kind, oid = record.decode().split()
102
+ if kind == 'tag':
103
+ raw = self.run('cat-file', 'tag', oid)
104
+ yield '@tag-message', oid, raw.partition(b'\n\n')[2]
105
+
106
+ def close(self):
107
+ if self.reader:
108
+ self.reader.stdin.close()
109
+ self.reader.stdout.close()
110
+ try:
111
+ self.reader.wait(timeout=2)
112
+ except subprocess.TimeoutExpired:
113
+ self.reader.kill()
114
+ self.reader.wait()
115
+ self.reader = None
@@ -0,0 +1,217 @@
1
+ """Resolve publication references with gh's local credentials; retain booleans only."""
2
+ import json
3
+ import os
4
+ import re
5
+ import subprocess
6
+ from repository_intent import declarations, commands
7
+
8
+ SLUG = r'[A-Za-z0-9][A-Za-z0-9_.-]{0,99}'
9
+ PAIR = SLUG + '/' + SLUG
10
+ URL_BOUNDARY = r'(?<![A-Za-z0-9_./@+-])'
11
+ TRANSPORT = r'(?:[a-z][a-z0-9+.-]*://|(?<!:)//)'
12
+ USERINFO = r'''(?:[^/\s@"'<>`]+@)?'''
13
+ PORT = r'(?::[0-9]+)?'
14
+ REPOSITORY_HOSTS = {'github.com': '/', 'www.github.com': '/', 'ssh.github.com': '/',
15
+ 'raw.githubusercontent.com': '/', 'api.github.com': '/repos/',
16
+ 'codeload.github.com': '/'}
17
+ REPOSITORY_HOST = '(?:' + '|'.join('(?a:' + re.escape(host) + ')' + PORT + route for host, route in REPOSITORY_HOSTS.items()) + ')'
18
+ # Embedded patterns retain ASCII scheme/host folding without changing the
19
+ # surrounding text and repository-path recognition used by privacy inspection.
20
+ URL = re.compile(r'(?i)' + URL_BOUNDARY + r'(?:(?a:' + TRANSPORT + ')' + USERINFO + REPOSITORY_HOST +
21
+ r'|(?a:git@github\.com:))(' + PAIR + r')(?![A-Za-z0-9_.-])')
22
+ FULL = re.compile(PAIR + r'\Z')
23
+ METADATA = re.compile(r'(?i)(?:^|/)(?:[^/]+\.(?:json|ya?ml|toml)|\.gitmodules)$')
24
+ # GitHub documents these web auth endpoints, not repository namespaces.
25
+ SERVICE_URL = re.compile(r'(?i)' + URL_BOUNDARY + r'(?a:https?://|(?<!:)//)' + USERINFO +
26
+ r'(?a:(?:www\.)?github\.com)' + PORT +
27
+ r'''/(?:login/oauth/(?:authorize|access_token)|login/device(?:/code)?)(?=[?#\s"'`<>)]|$)''')
28
+ # Only the complete web endpoint is a service; transports and repository
29
+ # declarations retain their original repository meaning.
30
+ SPONSORS_URL = re.compile(URL_BOUNDARY + r'(?a:https?://|(?<!:)//)' + USERINFO +
31
+ r'(?a:(?:www\.)?github\.com)' + PORT +
32
+ r'''/(?-i:sponsors)/(?a:[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)/?(?=[?#\s"'`<>)]|$)''', re.I)
33
+ FILE_SUFFIX = re.compile(r'(?i)\.(?:xml|json|ya?ml|toml|[cm]?[jt]sx?|md|txt|html|css|sql|png|jpe?g|svg|zip|csv|pdf|docx|xlsx|rer)$')
34
+ DEPENDENCY = re.compile(r'(?i)\bgithub:(' + PAIR + r')(?![A-Za-z0-9_./-])')
35
+ ACTION = re.compile(r'''(?im)^\s*(?:-\s*)?uses\s*:\s*["']?(''' + PAIR + r''')(?:/[A-Za-z0-9_./-]+)?@[^\s"']+''')
36
+ REPOSITORY_FIELD = re.compile(r'''(?im)(?<![\w$])["'`]?(?:repository|repo|github|sourceRepository)["'`]?\s*[:=]\s*["'`]?(''' + PAIR + r''')(?![A-Za-z0-9_./-])''')
37
+ # A code span such as `dist/client` names a path in the project, not an owner/repo.
38
+ LOCAL_DIRECTORIES = frozenset({'dist', 'src', 'scripts', 'public', 'tests', 'docs', 'exports', 'node_modules', '.rer', '.seed'})
39
+ SOURCE = re.compile(r'(?i)\.[cm]?[jt]sx?$')
40
+ TYPED_REPOSITORY = re.compile(r'''(?im)(?<![\w$])(?:repository|repo|github|sourceRepository)\s*:\s*string\s*=\s*["'`](''' + PAIR + r''')["'`]''')
41
+ DECLARED_SHORTHAND = re.compile(r'''(?im)(?<![\w$])["'`]?(?:url|fullURL)["'`]?\s*[:=]\s*["'`](''' + PAIR + r''')["'`]''')
42
+ REPOSITORY_ROUTE = re.compile(r'/(?:tree|blob|issues|pull|pulls|commit|commits|releases|actions|wiki|discussions|compare|tags)(?:/|$)')
43
+ SHORTHAND = PAIR + r'(?:[/?#][^\s`<>]*)?'
44
+ REPOSITORY_CONTEXT = r'github|repository|repo|source|upstream|downstream|fork(?:ed)?(?:\s+(?:from|of|into))?|clone(?:d)?(?:\s+from)?|gh\s+repo\s+(?:clone|view|fork)'
45
+ MARKUP = r'[`*_~\[\]]*'
46
+ MARKDOWN_REFERENCE = re.compile(r'`(?P<code>' + SHORTHAND + r')`|(?<![A-Za-z])(?P<label>(?i:' + REPOSITORY_CONTEXT + r'))(?:\s+(?:repository|repo))?' + MARKUP + r'(?P<separator>\s*[:=|]\s*|\s+)(?P<markup>' + MARKUP + r')\s*(?P<reference>' + SHORTHAND + r')(?![A-Za-z0-9_./-])')
47
+
48
+ EXPLICIT_URL = re.compile(r'''(?im)["']?(?:repository|repo|github|sourceRepository)["']?\s*[:=]\s*(?:\{\s*["']?(?:url|fullURL)["']?\s*:\s*)?[`*_~\["']*\s*(''' + URL.pattern.replace('(?i)', '') + ')', re.I)
49
+ CONTEXT_URL = re.compile(r'(?i)(?<![A-Za-z])(?:' + REPOSITORY_CONTEXT + r')'
50
+ r'(?:\s+(?:repository|repo))?' + MARKUP + r'(?:\s*[:=|]\s*|\s+)'
51
+ + r'''[`*_~\[\]\s"']*(?:''' + URL.pattern.replace('(?i)', '') + ')')
52
+ LINK_URL = re.compile(r'(?i)\[(?P<label>[^]\n]+)\]\(\s*(?P<url>' + URL.pattern.replace('(?i)', '') + ')')
53
+ REPOSITORY_LABEL = re.compile(r'(?i)\b(?:' + REPOSITORY_CONTEXT + r')\b')
54
+
55
+
56
+ def explicit_references(text, path):
57
+ found = {normalize(m.group(2)) for m in EXPLICIT_URL.finditer(text)}
58
+ found.update(normalize(m.group(1)) for m in CONTEXT_URL.finditer(text))
59
+ for match in LINK_URL.finditer(text):
60
+ if REPOSITORY_LABEL.search(match['label']):
61
+ found.update(normalize(m.group(1)) for m in URL.finditer(match['url']))
62
+ for declaration in (*declarations(text), *commands(text)):
63
+ found.update(normalize(m.group(1)) for m in URL.finditer(declaration))
64
+ return found
65
+
66
+
67
+ def normalize(value):
68
+ return value.lower().rstrip('.').removesuffix('.git')
69
+
70
+
71
+ class RepositoryObject(dict):
72
+ """Retain duplicate JSON fields when checking explicit reference context."""
73
+ def __init__(self, pairs):
74
+ super().__init__(pairs)
75
+ self.pairs = pairs
76
+
77
+
78
+ def references(text, path, local_paths=frozenset(), project_identity=None):
79
+ repository_text = SERVICE_URL.sub('', text)
80
+ found = {normalize(m.group(1)) for m in URL.finditer(repository_text)
81
+ if not SPONSORS_URL.match(repository_text, m.start())}
82
+ # A URL in an explicit repository declaration has stronger context than
83
+ # its coincident service spelling. Never erase the original text first.
84
+ found.update(explicit_references(repository_text, path))
85
+ found.update(normalize(m.group(1)) for m in DEPENDENCY.finditer(text))
86
+ if path.lower().endswith(('.yml', '.yaml', '.md', '.markdown')):
87
+ found.update(normalize(m.group(1)) for m in ACTION.finditer(text))
88
+ # Repository fields avoid mistaking module IDs for repository references.
89
+ # Markdown slash prose needs an explicit code span or repository context.
90
+ if path.lower().endswith(('.md', '.markdown')) or path in ('@commit-message', '@tag-message'):
91
+ markdown = URL.sub('', text)
92
+ markdown = re.sub(r'\]\((?![A-Za-z][A-Za-z0-9+.-]*://)[^)]*\)', ']()', markdown)
93
+ for match in MARKDOWN_REFERENCE.finditer(markdown):
94
+ explicit = match['reference'] is not None
95
+ value = re.split(r'[?#]', match['reference'] or match['code'], maxsplit=1)[0]
96
+ # Underscores are also legal slug characters; remove a closing
97
+ # emphasis marker only when the reference has its matching opener.
98
+ emphasis = re.search(r'_+$', match['markup'] or '')
99
+ if emphasis and value.endswith(emphasis.group()):
100
+ value = value[:-len(emphasis.group())]
101
+ repository = '/'.join(value.split('/')[:2])
102
+ suffix = value[len(repository):]
103
+ if not explicit and (value in local_paths or repository in local_paths or FILE_SUFFIX.search(repository)
104
+ or repository.split('/')[0] in LOCAL_DIRECTORIES):
105
+ continue
106
+ # A plain multi-segment path is ambiguous; GitHub's repository
107
+ # routes, a labeled field or a GitHub command disambiguate it.
108
+ labeled = explicit and (re.search(r'[:=|]', match['separator']) or re.match(r'(?i)github|gh\s', match['label']))
109
+ if not suffix or REPOSITORY_ROUTE.match(suffix) or labeled:
110
+ found.add(normalize(repository))
111
+ elif METADATA.search(path) or SOURCE.search(path):
112
+ if path.lower().endswith('.json'):
113
+ try:
114
+ document = json.loads(text, object_pairs_hook=RepositoryObject)
115
+ except ValueError:
116
+ document = None
117
+ identity_field = {'edition.json': 'id', 'sources.lock.json': 'edition', 'rer-project.json': 'projectId'}.get(path.rsplit('/', 1)[-1])
118
+ identity = document.get(identity_field) if isinstance(document, dict) and identity_field else None
119
+ logical = path in ('edition.json', 'sources.lock.json', 'rer-project.json') and identity == project_identity
120
+ if isinstance(identity, str) and FULL.fullmatch(identity) and not logical:
121
+ found.add(normalize(identity))
122
+ def visit(value):
123
+ if isinstance(value, dict):
124
+ for key, item in value.pairs:
125
+ if key.lower() in ('repository', 'repo', 'github', 'sourcerepository'):
126
+ candidate = item.get('url') if isinstance(item, dict) else item
127
+ if isinstance(candidate, str) and FULL.fullmatch(candidate):
128
+ found.add(normalize(candidate))
129
+ def declared(item):
130
+ if isinstance(item, str):
131
+ found.update(normalize(m.group(1)) for m in URL.finditer(SERVICE_URL.sub('', item)))
132
+ elif isinstance(item, dict):
133
+ for _, child in item.pairs:
134
+ declared(child)
135
+ elif isinstance(item, list):
136
+ for child in item:
137
+ declared(child)
138
+ declared(item)
139
+ visit(item)
140
+ elif isinstance(value, list):
141
+ for item in value:
142
+ visit(item)
143
+ visit(document)
144
+ found.update(normalize(m.group(1)) for m in REPOSITORY_FIELD.finditer(text))
145
+ if SOURCE.search(path):
146
+ found.update(normalize(m.group(1)) for m in TYPED_REPOSITORY.finditer(text))
147
+ for declaration in declarations(text):
148
+ found.update(normalize(m.group(1)) for m in DECLARED_SHORTHAND.finditer(declaration))
149
+ return found
150
+
151
+
152
+ def local_dependencies(data, path):
153
+ """Cache only local-path memberships that can change this blob's meaning."""
154
+ if not path.lower().endswith(('.md', '.markdown')):
155
+ return frozenset()
156
+ from hygiene_text import decoded_text
157
+ result = set()
158
+ for match in MARKDOWN_REFERENCE.finditer(decoded_text(data)):
159
+ value = re.split(r'[?#]', match['reference'] or match['code'], maxsplit=1)[0]
160
+ result.update((value, '/'.join(value.split('/')[:2])))
161
+ return frozenset(result)
162
+
163
+
164
+ def response_object(pairs):
165
+ document = {}
166
+ for key, value in pairs:
167
+ if key in document:
168
+ raise ValueError('duplicate API response key')
169
+ document[key] = value
170
+ return document
171
+
172
+
173
+ def response_constant(value):
174
+ raise ValueError('nonstandard API JSON constant')
175
+
176
+
177
+ def visibility(output, exit_code):
178
+ headers, body = re.split(r'\r?\n\r?\n', output, maxsplit=1)
179
+ status = re.fullmatch(r'HTTP/(?:1\.[01]|2(?:\.0)?|3(?:\.0)?) (\d{3})(?: [^\r\n]*)?',
180
+ headers.splitlines()[0])
181
+ if not status:
182
+ return 'incomplete'
183
+ code = int(status.group(1))
184
+ document = json.loads(body, object_pairs_hook=response_object, parse_constant=response_constant)
185
+ if not isinstance(document, dict):
186
+ return 'incomplete'
187
+ if code == 200 and exit_code == 0 and type(document.get('private')) is bool:
188
+ return 'private' if document['private'] else 'public'
189
+ # gh exits 1 for HTTP errors. A header alone or an arbitrary error document
190
+ # is not evidence of a missing repository, even at the intended destination.
191
+ if (code == 404 and exit_code == 1 and document.get('message') == 'Not Found' and
192
+ document.get('status') == '404' and isinstance(document.get('documentation_url'), str) and
193
+ re.fullmatch(r'https://docs\.github\.com/rest/[^\s]+', document['documentation_url'])):
194
+ return 'missing'
195
+ return 'incomplete'
196
+
197
+
198
+ class GitHubResolver:
199
+ def __init__(self, spool=None):
200
+ self.cache = {}
201
+
202
+ def resolve(self, repository):
203
+ if repository in self.cache:
204
+ return self.cache[repository]
205
+ # No disk cache, auth arguments, diagnostic output or response metadata.
206
+ # Disable gh debug logging even if inherited from the calling shell.
207
+ env = {k: v for k, v in os.environ.items() if k not in ('GH_DEBUG', 'DEBUG')}
208
+ env['GH_PROMPT_DISABLED'] = '1'
209
+ try:
210
+ response = subprocess.run(['gh', 'api', '--hostname', 'github.com', '--include', 'repos/' + repository],
211
+ env=env, capture_output=True, timeout=60)
212
+ output = response.stdout.decode('utf-8')
213
+ result = visibility(output, response.returncode)
214
+ except (OSError, subprocess.TimeoutExpired, ValueError, IndexError):
215
+ result = 'incomplete'
216
+ self.cache[repository] = result
217
+ return result
@@ -0,0 +1,72 @@
1
+ """Pinned Gitleaks is the only secret detection engine."""
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ import shutil
6
+ import subprocess
7
+ import tempfile
8
+ from git_history import InspectionError
9
+
10
+ VERSION = '8.30.1'
11
+ CONFIG = Path(__file__).with_name('gitleaks.toml')
12
+
13
+
14
+ def executable():
15
+ binary = shutil.which('gitleaks') or str(Path.home() / '.local/bin/gitleaks')
16
+ try:
17
+ version = subprocess.run([binary, 'version'], capture_output=True, timeout=10)
18
+ if version.returncode or version.stdout.decode().strip() != VERSION:
19
+ raise InspectionError('scan.gitleaks-version')
20
+ except OSError:
21
+ raise InspectionError('scan.gitleaks-unavailable') from None
22
+ return binary
23
+
24
+
25
+ def inspect(binary, mode, repo=None, revisions=None, data=None, timeout=300, redact=True):
26
+ with tempfile.TemporaryDirectory(prefix='publish-gitleaks-') as directory:
27
+ report = Path(directory) / 'report.json'
28
+ report.touch(mode=0o600)
29
+ config = CONFIG
30
+ if mode == 'dir':
31
+ config = Path(directory) / 'directory.toml'
32
+ # Extending a config and defining a global allowlist replaces its
33
+ # global rules. Keep the maintained policy intact in both modes;
34
+ # append only the directory exclusions already used here.
35
+ config.write_text(CONFIG.read_text() + '\n[[allowlists]]\npaths = [\"(^|/)(\\\\.git|node_modules)(/|$)\"]\n')
36
+ args = [binary, mode, '--config', str(config), '--redact=' + ('100' if redact else '0'), '--no-banner', '--no-color',
37
+ '--ignore-gitleaks-allow', '--gitleaks-ignore-path', directory, '--report-format', 'json',
38
+ '--report-path', str(report), '--timeout', str(max(1, int(timeout)))]
39
+ if mode == 'git':
40
+ args += ['--log-opts=' + ' '.join(['--full-history', '-m', '--no-ext-diff', '--no-textconv', *revisions])]
41
+ if repo is not None:
42
+ args.append(repo)
43
+ env = {k: v for k, v in os.environ.items() if not k.startswith(('GITLEAKS_', 'GIT_'))}
44
+ env.update(GIT_NO_REPLACE_OBJECTS='1', GIT_TERMINAL_PROMPT='0')
45
+ try:
46
+ result = subprocess.run(args, input=data, capture_output=True, env=env, timeout=timeout)
47
+ if result.returncode not in (0, 1):
48
+ raise InspectionError('scan.gitleaks-failed')
49
+ # Native reports may contain raw matches for location-bound proof.
50
+ # The enclosing temporary directory is private; values never enter
51
+ # the caller-facing report or logs. Gitleaks replaces file mode.
52
+ report.chmod(0o600)
53
+ findings = json.loads(report.read_text())
54
+ if not isinstance(findings, list):
55
+ raise ValueError()
56
+ return findings
57
+ except subprocess.TimeoutExpired:
58
+ raise InspectionError('scan.deadline-exceeded') from None
59
+ except (OSError, ValueError):
60
+ raise InspectionError('scan.gitleaks-failed') from None
61
+
62
+
63
+ def rule(identifier):
64
+ if identifier in ('generic-api-key', 'hex-api-key'):
65
+ return 'secret.assignment'
66
+ if 'private-key' in identifier:
67
+ return 'secret.private-key'
68
+ if 'jwt' in identifier:
69
+ return 'secret.jwt'
70
+ if 'password' in identifier or 'connection-string' in identifier:
71
+ return 'secret.url-credentials'
72
+ return 'secret.provider-token'
@@ -0,0 +1,40 @@
1
+ title = "Base publication scan"
2
+ [extend]
3
+ useDefault = true
4
+
5
+ [[allowlists]]
6
+ description = "Explicit synthetic credential values; no exemption by file or directory"
7
+ regexes = ['''(?i)^(?:test|fixture|fake|example|dummy|placeholder|your)[_-]''', '''(?i)^(?:gh[pousr]_|sk-(?:proj-|ant-)?|glpat-|npm_|whsec_|rk_live_|sk_live_|hf_|xapp-)(?:test|fixture|fake|example|dummy|placeholder)[_-]''']
8
+
9
+ [[allowlists]]
10
+ description = "Explicit synthetic values used by redaction fixtures"
11
+ regexes = ['''(?i)^synthetic[_-]''']
12
+
13
+ [[rules]]
14
+ id = "private-key"
15
+ [[rules.allowlists]]
16
+ description = "A PEM-shaped fixture starts its body with a synthetic marker"
17
+ regexes = ['''(?i)^-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY(?: BLOCK)?-----(?:\s|\\[nr])+(?:test|fixture|fake|example|dummy|placeholder|synthetic)[_-]''']
18
+
19
+ [[rules.allowlists]]
20
+ description = "A private-key header literal without an attached body is not a stored key"
21
+ regexTarget = "match"
22
+ regexes = ['''(?i)^-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY(?: BLOCK)?-----(?:\s|\\[nr])*['"]''']
23
+
24
+ [[rules]]
25
+ id = "jwt"
26
+ [[rules.allowlists]]
27
+ description = "A JWT-shaped fixture explicitly marks the signature synthetic"
28
+ regexes = ['''\.(?:test|fixture|fake|example|dummy|placeholder|synthetic)[_-][a-zA-Z0-9_-]+$''']
29
+
30
+ # SHA-shaped credentials must not disappear because their random bytes contain
31
+ # an English stopword (for example dead/feed) or fall below generic entropy.
32
+ # Source/module identities still require the location-bound proof in Python.
33
+ [[rules]]
34
+ id = "hex-api-key"
35
+ description = "Hexadecimal credential assignment"
36
+ regex = '''(?i)[\w.-]{0,50}?(?:access|auth|(?-i:[Aa]pi|API)|credential|creds|key|passw(?:or)?d|secret|token)(?:[ \t\w.-]{0,20})[\s'"]{0,3}(?:=|>|:{1,3}=|\|\||:|=>|\?=|,)[\x60'"\s=]{0,5}([a-f0-9]{64}|[a-f0-9]{40})(?:[\x60'"\s;]|\\[nr]|$)'''
37
+ keywords = ["access", "auth", "api", "credential", "creds", "key", "passwd", "password", "secret", "token"]
38
+
39
+ [[rules]]
40
+ id = "generic-api-key"
@@ -0,0 +1,176 @@
1
+ """Scan current files and full history using native Git and pinned Gitleaks."""
2
+ from pathlib import Path
3
+ from git_history import History, InspectionError
4
+ from public_base import PublicBaseError
5
+ from gitleaks import executable, inspect as secrets, rule as secret_rule
6
+ from hygiene_config import CONFIG, load_config
7
+ from github_refs import GitHubResolver, local_dependencies
8
+ from destination import destination, INVALID
9
+ from env_values import load_hashes
10
+ from licenses import EXCEPTIONS, exceptions, license_rules, registry
11
+ from reviewed import REVIEWED, load_reviewed
12
+ from report import new_report, Findings, finish
13
+ from tree_scan import context, text_rules, working_files, working_regular
14
+ from content_identities import ContentIdentities, candidate as identity_candidate
15
+ from checksum_maps import ChecksumMaps
16
+
17
+
18
+ def scan(repo, ref='HEAD', public_base=None, env_file=None, project=None, repository=None, *, cache_dir=None, timeout=300):
19
+ report = new_report(public_base is not None)
20
+ history = None
21
+ add = Findings(report, {}, frozenset()).add
22
+ try:
23
+ hashes = load_hashes(env_file)
24
+ report['guarantees']['environmentChecked'] = env_file is not None
25
+ binary = executable()
26
+ history = History(repo, ref, public_base)
27
+ report['source'] = dict(commit=history.commit, tree=history.commit_tree(history.commit).decode())
28
+ report['publicBase'] = dict(ref=history.public_label, commit=history.public_commit) if public_base else None
29
+ config = load_config(history.selected(CONFIG))
30
+ reviewed_bytes = history.selected(REVIEWED)
31
+ reviewed = load_reviewed(reviewed_bytes) if reviewed_bytes else {}
32
+ add = Findings(report, reviewed, hashes).add
33
+ try:
34
+ config['pendingDestination'] = destination(history, project, repository)
35
+ except (ValueError, TypeError, UnicodeError):
36
+ add(INVALID, 'edition.json', history.commit)
37
+ config['pendingDestination'] = None
38
+ recorded = registry() + exceptions(history.selected(EXCEPTIONS))
39
+ resolver = GitHubResolver()
40
+ public_blobs = {e['oid'] for c in history.public_commits for e in history.entries(c)}
41
+ report['history']['preExistingCommits'] = sorted(history.public_commits)
42
+ report['history']['newlyPublicCommits'] = [c for c in history.commits if c not in history.public_commits]
43
+ seen_trees, seen_blobs, inspected = set(), set(), {}
44
+ entries_by_commit, dependencies = {}, {}
45
+ release_license_good = not license_rules(history.selected('LICENSE'), recorded)
46
+ for commit in history.commits:
47
+ entries = history.entries(commit)
48
+ entries_by_commit[commit] = {e['path']: e for e in entries}
49
+ tree = history.commit_tree(commit).decode()
50
+ seen_trees.add(tree)
51
+ scope = 'release-tree' if commit == history.commit else 'pre-existing' if commit in history.public_commits else 'newly-public'
52
+ config_tree, invalid = context(history, commit, config)
53
+ for path in invalid:
54
+ add(INVALID if (project is not None or repository is not None) and path == 'edition.json' else 'scan.identity-invalid', path, commit, scope=scope)
55
+ license_entry = entries_by_commit[commit].get('LICENSE')
56
+ license_oid = license_entry['oid'] if license_entry and license_entry['mode'] in ('100644', '100755') else None
57
+ for rule in license_rules(history.blob(license_oid) if license_oid else None, recorded):
58
+ bucket = 'warnings' if rule == 'license.missing' and public_base and scope == 'newly-public' and release_license_good else None
59
+ add(rule, 'LICENSE', commit, license_oid, scope, bucket)
60
+ signature = (config_tree['declaredSources'], config_tree['projectIdentity'], frozenset(config_tree['trustBundles'].items()))
61
+ for entry in entries:
62
+ path, oid = entry['path'], entry['oid']
63
+ seen_blobs.add(oid)
64
+ if entry['kind'] != 'blob':
65
+ add('scan.submodule-uninspected', path, commit, oid, scope)
66
+ continue
67
+ pair = (path, oid)
68
+ if pair not in dependencies:
69
+ data = history.blob(oid)
70
+ dependencies[pair] = local_dependencies(data, path)
71
+ local = dependencies[pair] & config_tree['localPaths']
72
+ key = (path, oid, signature, local)
73
+ if key not in inspected:
74
+ inspected[key] = text_rules(history.blob(oid), path, config_tree, resolver, hashes)
75
+ rules, provenance = inspected[key]
76
+ entry_scope = 'pre-existing' if scope != 'release-tree' and oid in public_blobs else scope
77
+ for rule in rules:
78
+ add(rule, path, commit, oid, entry_scope)
79
+ if provenance:
80
+ add('provenance.declared-source', path, commit, oid, entry_scope)
81
+ report['history'].update(commits=len(history.commits), trees=len(seen_trees), blobs=len(seen_blobs))
82
+ selected_context, _ = context(history, history.commit, config)
83
+ working_paths = []
84
+ working_identities = {}
85
+ for path, data in working_files(Path(history.repo)):
86
+ working_paths.append(path)
87
+ target = Path(history.repo) / path
88
+ if identity_candidate(path) and not any(p.is_symlink() for p in [target, *target.parents]):
89
+ working_identities[path] = data
90
+ # Native hash-object is unnecessary here: unchanged current bytes use
91
+ # their known immutable identity; new bytes have no history identity.
92
+ entry = entries_by_commit[history.commit].get(path)
93
+ oid = entry['oid'] if entry and entry['kind'] == 'blob' and history.blob(entry['oid']) == data else None
94
+ rules, _ = text_rules(data, path, selected_context, resolver, hashes)
95
+ for rule in rules:
96
+ add(rule, path, history.commit, oid, 'working-tree')
97
+ if path == 'LICENSE':
98
+ for rule in license_rules(data, recorded):
99
+ add(rule, path, history.commit, oid, 'working-tree')
100
+ if not (Path(history.repo) / 'LICENSE').is_file():
101
+ add('license.missing', 'LICENSE', scope='working-tree')
102
+ identities = {'working-tree': ContentIdentities(working_identities,
103
+ lambda path: working_regular(Path(history.repo), path))}
104
+ checksums = {}
105
+ for mode in ('git', 'dir'):
106
+ for hit in secrets(binary, mode, history.repo, history.revisions, timeout=timeout, redact=False):
107
+ path, commit = hit.get('File', '.'), hit.get('Commit') or history.commit
108
+ if mode == 'dir' and Path(path).is_absolute():
109
+ path = Path(path).resolve().relative_to(Path(history.repo).resolve()).as_posix()
110
+ entry = entries_by_commit.get(commit, {}).get(path)
111
+ oid = entry['oid'] if entry and entry['kind'] == 'blob' else None
112
+ scope = 'working-tree' if mode == 'dir' else 'release-tree' if commit == history.commit else 'pre-existing' if oid in public_blobs else 'newly-public'
113
+ if hit['RuleID'] in ('generic-api-key', 'hex-api-key'):
114
+ identity_scope = 'working-tree' if mode == 'dir' else commit
115
+ scoped_entries = entries_by_commit.get(commit, {})
116
+ def read_regular(path, scoped_entries=scoped_entries):
117
+ entry = scoped_entries.get(path)
118
+ return (history.blob(entry['oid']), entry['mode']) if entry and entry['kind'] == 'blob' and entry['mode'] in ('100644', '100755') else None
119
+ if identity_scope not in checksums:
120
+ checksums[identity_scope] = ChecksumMaps(
121
+ working_paths if mode == 'dir' else scoped_entries,
122
+ (lambda path: working_regular(Path(history.repo), path)) if mode == 'dir' else read_regular)
123
+ if identity_candidate(path):
124
+ if identity_scope not in identities:
125
+ identities[identity_scope] = ContentIdentities({
126
+ p: history.blob(e['oid']) for p, e in scoped_entries.items()
127
+ if identity_candidate(p) and e['kind'] == 'blob' and e['mode'] in ('100644', '100755')}, read_regular)
128
+ if identities[identity_scope].matches(path, hit):
129
+ continue
130
+ else:
131
+ # Content-identity documents keep their own stricter proof.
132
+ held = working_regular(Path(history.repo), path) if mode == 'dir' else read_regular(path)
133
+ if held and checksums[identity_scope].matches(held[0], hit):
134
+ continue
135
+ add(secret_rule(hit['RuleID']), path, commit, oid, scope, scanner_rule=hit['RuleID'])
136
+ messages, lines = [], []
137
+ count = 1
138
+ all_paths = dict.fromkeys(working_paths, history.commit)
139
+ for commit, entries in entries_by_commit.items():
140
+ for path in entries:
141
+ all_paths.setdefault(path, commit)
142
+ for path, commit in sorted(all_paths.items()):
143
+ data = path.encode('utf-8', 'surrogateescape') + b'\n'
144
+ messages.append(data)
145
+ length = data.count(b'\n')
146
+ scope = 'working-tree' if path in working_paths else 'pre-existing' if commit in history.public_commits else 'newly-public'
147
+ lines.append((count, count + length, path, commit, scope))
148
+ count += length
149
+ for path, commit, data in history.messages():
150
+ report['history']['tagMessages' if path == '@tag-message' else 'commitMessages'] += 1
151
+ rules, _ = text_rules(data, path, selected_context, resolver, hashes)
152
+ scope = 'release-tree' if commit == history.commit else 'pre-existing' if commit in history.public_commits else 'newly-public'
153
+ for rule in rules:
154
+ add(rule, path, commit, scope=scope)
155
+ messages.append(data + b'\n')
156
+ length = (data + b'\n').count(b'\n')
157
+ lines.append((count, count + length, path, commit, scope))
158
+ count += length
159
+ for hit in secrets(binary, 'stdin', data=b''.join(messages), timeout=timeout):
160
+ line = hit.get('StartLine', 0)
161
+ location = next(((p, c, s) for start, end, p, c, s in lines if start <= line < end), ('@message', None, 'newly-public'))
162
+ add(secret_rule(hit['RuleID']), location[0], location[1], scope=location[2], scanner_rule=hit['RuleID'])
163
+ except PublicBaseError:
164
+ add('scan.public-base-rejected', '.')
165
+ except InspectionError as error:
166
+ if str(error).startswith('scan.'):
167
+ add(str(error), '.')
168
+ else:
169
+ add('scan.history-incomplete', '.')
170
+ report['findings'][-1]['reason'] = str(error)
171
+ except (OSError, ValueError, TypeError, KeyError, UnicodeError):
172
+ add('scan.configuration-incomplete', '.')
173
+ finally:
174
+ if history:
175
+ history.close()
176
+ return finish(report)