@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,18 @@
1
+ """Resolve a native Gitleaks hit to the exact byte spans of its match."""
2
+
3
+
4
+ def match_spans(data, hit):
5
+ lines = data.splitlines(keepends=True)
6
+ try:
7
+ first, last = hit['StartLine'] - 1, hit['EndLine'] - 1
8
+ if not (0 <= first <= last < len(lines)):
9
+ return []
10
+ start = sum(map(len, lines[:first])) + hit['StartColumn'] - 1
11
+ end = sum(map(len, lines[:last])) + hit['EndColumn']
12
+ # Gitleaks 8.30.1 includes the preceding newline in columns
13
+ # after fragment line one; Git fragments may begin mid-file.
14
+ # Resolve that one-byte difference only against the exact match.
15
+ return [(start - shift, end - shift) for shift in (0, 1)
16
+ if data[start - shift:end - shift] == hit['Match'].encode('utf-8')]
17
+ except (KeyError, TypeError, IndexError, UnicodeError):
18
+ return []
@@ -0,0 +1,42 @@
1
+ """Base-owned defaults and explicit committed seed publication declarations."""
2
+ import json
3
+ import re
4
+ from publication_config import intended_public_repositories
5
+
6
+ # A home segment within a relative path is not an absolute home directory.
7
+ # Match the username itself so a bare home, including one with spaces, counts.
8
+ LOCAL_PATH = re.compile(r'''(?x)(?<![^\s("'`=:\[<>{|])(?:[*_]|~~)*(?:(?:(?i:(?a:file://)))?(?<!/[A-Z]:)/(?:Users|home)/|(?i:(?:(?a:file:///))?[A-Z]:[\\/]+Users[\\/]+))[^/\\\s"'<>`]+''')
9
+ CONFIG = '.publish-check/config.json'
10
+ # An alphabetic top-level label ends before a following digit, such as the DER
11
+ # tag after a certificate's email value; other labels keep the full extent.
12
+ EMAIL = re.compile(r"(?<![A-Za-z0-9_.+%-])[A-Za-z0-9_][A-Za-z0-9_.+%'-]{0,63}@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)*[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.(?:[A-Za-z]{2,63}(?![A-Za-z.-])|[A-Za-z][A-Za-z0-9-]{1,62}(?![A-Za-z0-9.-]))|(?<![A-Za-z0-9_.+%-])[A-Za-z0-9_][A-Za-z0-9_.+%'-]{0,63}@(?:example|test|invalid)(?![A-Za-z0-9.-])", re.I)
13
+ RULES = {'hygiene.local-path', 'hygiene.github-private', 'hygiene.github-missing',
14
+ 'hygiene.email', 'hygiene.internal-reference'}
15
+
16
+
17
+ def load_config(data=None):
18
+ config = json.loads(data) if data is not None else {'schemaVersion': 1}
19
+ if not isinstance(config, dict) or config.get('schemaVersion') != 1 or set(config) - {'schemaVersion', 'publicEmails', 'internalReferencePatterns'}:
20
+ raise ValueError('invalid publication config')
21
+ emails = config.get('publicEmails', [])
22
+ extra_patterns = config.get('internalReferencePatterns', [])
23
+ if not isinstance(extra_patterns, list):
24
+ raise ValueError('invalid internal references')
25
+ patterns = extra_patterns
26
+ if not isinstance(emails, list) or any(not isinstance(e, str) or not EMAIL.fullmatch(e) or '*' in e for e in emails):
27
+ raise ValueError('invalid public emails')
28
+ if not isinstance(patterns, list) or any(not isinstance(p, str) or not p for p in patterns):
29
+ raise ValueError('invalid internal references')
30
+ try:
31
+ compiled = [re.compile(p) for p in patterns]
32
+ except re.error:
33
+ raise ValueError('invalid internal reference regex') from None
34
+ return {'publicEmails': {e.lower() for e in emails}, 'internalReferencePatterns': compiled,
35
+ 'intendedPublicRepositories': intended_public_repositories()}
36
+
37
+
38
+ def public_email(address, config):
39
+ domain = address.lower().rsplit('@', 1)[1]
40
+ # RFC 2606 reserved names; .localhost also holds local dev personas (<as>@dev.localhost).
41
+ reserved = ('example', 'test', 'invalid', 'localhost', 'example.com', 'example.net', 'example.org')
42
+ return address.lower() in config['publicEmails'] or any(domain == d or domain.endswith('.' + d) for d in reserved)
@@ -0,0 +1,11 @@
1
+ """Decode common textual representations without executing source."""
2
+ import html
3
+ import re
4
+ from urllib.parse import unquote
5
+
6
+
7
+ def decoded_text(data):
8
+ text = data.replace(b'\0', b'').decode('utf-8', 'replace')
9
+ text = html.unescape(unquote(text))
10
+ return re.sub(r'\\(?:u([0-9a-fA-F]{4})|x([0-9a-fA-F]{2}))',
11
+ lambda match: chr(int(match.group(1) or match.group(2), 16)), text).replace('\\/', '/')
@@ -0,0 +1,54 @@
1
+ // Validate inert committed JSON with the maintained recipe implementation.
2
+ // Never import or execute a tool from the repository being inspected.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {manifest, VERSION} from '../../edition-authoring/manifest.mjs';
7
+ import {hash, json} from '../../edition-authoring/files.mjs';
8
+
9
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
10
+ const identity = value => typeof value === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value) && !/\s/.test(value);
11
+ function optionalArray(value, key) {
12
+ if (key in value && !Array.isArray(value[key])) throw Error('Invalid array');
13
+ }
14
+ function origin(value) {
15
+ if (!object(value) || typeof value.repository !== 'string' || typeof value.revision !== 'string' || value.revision.length !== 40) throw Error('Invalid origin');
16
+ optionalArray(value, 'paths');
17
+ }
18
+ function shape(recipe) {
19
+ if (!object(recipe) || recipe.identityKind !== 'project' || !identity(recipe.id) || !object(recipe.toolchain)) throw Error('Invalid identity');
20
+ if (recipe.parent !== undefined && recipe.parent !== null) origin(recipe.parent);
21
+ if (!Array.isArray(recipe.components)) throw Error('Invalid components');
22
+ for (const component of recipe.components) {
23
+ if (!object(component) || !identity(component.id) || !object(component.source)) throw Error('Invalid component');
24
+ for (const key of ['requires', 'conflicts']) {
25
+ optionalArray(component, key);
26
+ if (component[key]?.some(value => typeof value !== 'string')) throw Error('Invalid dependency');
27
+ }
28
+ const selected = component.source;
29
+ if (selected.kind === 'git') origin(selected);
30
+ else if (selected.kind === 'local') {
31
+ if (selected.origin !== undefined) origin(selected.origin);
32
+ optionalArray(selected, 'mappings');
33
+ if (selected.mappings?.some(value => !object(value))) throw Error('Invalid mapping');
34
+ }
35
+ }
36
+ }
37
+
38
+ let directory;
39
+ try {
40
+ const data = fs.readFileSync(0, 'utf8');
41
+ const recipe = JSON.parse(data);
42
+ shape(recipe);
43
+ if (VERSION !== '1.1.0') throw Error('Unsupported recipe validator');
44
+ directory = fs.mkdtempSync(path.join(os.tmpdir(), 'publish-identity-'));
45
+ fs.writeFileSync(path.join(directory, 'edition.json'), data);
46
+ // manifest currently returns the complete parsed recipe, including metadata.
47
+ // Use its exact JSON.stringify(..., null, 2) + newline hash, as cli lock does.
48
+ process.stdout.write(hash(json(manifest(directory))));
49
+ } catch {
50
+ // Input, paths and diagnostics are intentionally absent from the report.
51
+ process.exitCode = 1;
52
+ } finally {
53
+ if (directory) fs.rmSync(directory, {recursive: true, force: true});
54
+ }
@@ -0,0 +1,79 @@
1
+ """Strict JSON documents and exact native-match byte locations."""
2
+ import json
3
+ import re
4
+
5
+ from hit_location import match_spans
6
+
7
+ HEX = re.compile(r'[a-f0-9]{64}\Z')
8
+ GIT_REVISION = re.compile(r'[a-f0-9]{40}\Z')
9
+
10
+
11
+ class Document:
12
+ """Reject ambiguous/non-JSON input and retain exact byte spans of strings."""
13
+ def __init__(self, data):
14
+ self.data = data
15
+ text = data.decode('utf-8')
16
+ self.spans = {}
17
+ def invalid_constant(_):
18
+ raise ValueError('Non-JSON constant')
19
+
20
+ decoder = json.JSONDecoder(parse_constant=invalid_constant)
21
+
22
+ def whitespace(at):
23
+ while at < len(text) and text[at] in ' \t\r\n':
24
+ at += 1
25
+ return at
26
+
27
+ def parse(at, pointer):
28
+ at = whitespace(at)
29
+ if text[at] in '{[':
30
+ mapping = text[at] == '{'
31
+ end = '}' if mapping else ']'
32
+ result = {} if mapping else []
33
+ at = whitespace(at + 1)
34
+ if text[at] == end:
35
+ return result, at + 1
36
+ while True:
37
+ if mapping:
38
+ key, at = decoder.raw_decode(text, at)
39
+ if not isinstance(key, str) or key in result:
40
+ raise ValueError('Ambiguous JSON member')
41
+ at = whitespace(at)
42
+ if text[at] != ':':
43
+ raise ValueError('Invalid JSON member')
44
+ at += 1
45
+ else:
46
+ key = len(result)
47
+ value, at = parse(at, (*pointer, key))
48
+ if mapping:
49
+ result[key] = value
50
+ else:
51
+ result.append(value)
52
+ at = whitespace(at)
53
+ if text[at] == end:
54
+ return result, at + 1
55
+ if text[at] != ',':
56
+ raise ValueError('Invalid JSON separator')
57
+ at = whitespace(at + 1)
58
+ value, end = decoder.raw_decode(text, at)
59
+ if isinstance(value, str) and (HEX.fullmatch(value) or GIT_REVISION.fullmatch(value)):
60
+ self.spans[pointer] = (len(text[:at].encode('utf-8')), len(text[:end].encode('utf-8')), value)
61
+ return value, end
62
+
63
+ self.value, end = parse(0, ())
64
+ if whitespace(end) != len(text):
65
+ raise ValueError('Trailing JSON data')
66
+
67
+ def matches(self, hit, pointers):
68
+ if hit.get('RuleID') not in ('generic-api-key', 'hex-api-key'):
69
+ return False
70
+ located = set()
71
+ for left, right in match_spans(self.data, hit):
72
+ for pointer in pointers:
73
+ span = self.spans.get(pointer)
74
+ if span:
75
+ a, b, value = span
76
+ if (left <= a + 1 and b - 1 <= right and hit.get('Secret') == value
77
+ and self.data[a + 1:b - 1] == value.encode('ascii')):
78
+ located.add((a, b))
79
+ return len(located) == 1
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "exceptions": [{
4
+ "rule": "license.holder",
5
+ "path": "LICENSE",
6
+ "holder": "Peter Industries Pty Ltd as trustee for Peter Family Trust, trading as JustEvery",
7
+ "date": "2026-10-06",
8
+ "sha256": "75de081fbc3da1ca679dff36ae665709d8c312652527d321511a10f66a17fd7c",
9
+ "reason": "Owner-authorized preservation of HappyToHelp's original attribution, exactly as shipped in public v0.2.1 and retained for the fast-forward v0.2.2 candidate. Applies only to these complete LICENSE bytes."
10
+ }]
11
+ }
@@ -0,0 +1,49 @@
1
+ """Root licence holder checks and narrowly bound, dated exceptions."""
2
+ from datetime import date, datetime, timezone
3
+ import hashlib
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+ EXCEPTIONS = 'PUBLISH-EXCEPTIONS.json'
9
+ HOLDER = re.compile(r'^\s*Copyright\s*(?:\(c\)|©)?\s*(?:\d{4}(?:\s*[-–,]\s*\d{4})*\s+)?(.+?)\s*$', re.I | re.M)
10
+
11
+
12
+ def exceptions(data):
13
+ if data is None:
14
+ return []
15
+ document = json.loads(data)
16
+ if document.get('schemaVersion') != 1 or not isinstance(document.get('exceptions'), list):
17
+ raise ValueError('invalid exceptions')
18
+ for entry in document['exceptions']:
19
+ if (entry.get('rule') != 'license.holder' or entry.get('path') != 'LICENSE'
20
+ or not isinstance(entry.get('holder'), str) or not entry['holder'].strip()
21
+ or not re.fullmatch(r'[0-9a-f]{64}', entry.get('sha256', ''))
22
+ or not isinstance(entry.get('reason'), str) or not entry['reason'].strip()
23
+ or not re.fullmatch(r'\d{4}-\d{2}-\d{2}', entry.get('date', ''))
24
+ or date.fromisoformat(entry['date']) > datetime.now(timezone.utc).date()):
25
+ raise ValueError('invalid exception')
26
+ return document['exceptions']
27
+
28
+
29
+ def license_rules(data, recorded):
30
+ if data is None or not data.strip():
31
+ return ['license.missing']
32
+ try:
33
+ text = data.decode('utf-8')
34
+ except UnicodeError:
35
+ return ['license.unreadable']
36
+ holders = HOLDER.findall(text)
37
+ if not holders:
38
+ return ['license.holder-missing']
39
+ digest = hashlib.sha256(data).hexdigest()
40
+ if any(holder != 'Run Edit Run' and not any(e['holder'] == holder and e['sha256'] == digest for e in recorded)
41
+ for holder in holders):
42
+ return ['license.holder']
43
+ return []
44
+
45
+
46
+ def registry():
47
+ """Base-reviewed exceptions, bound to holder and complete LICENSE bytes."""
48
+ path = Path(__file__).with_name('license-exceptions.json')
49
+ return exceptions(path.read_bytes())
@@ -0,0 +1,18 @@
1
+ """Report redaction, separate from the Gitleaks secret detector."""
2
+ import hashlib
3
+ import re
4
+ from hygiene_config import EMAIL, LOCAL_PATH
5
+ from hygiene_text import decoded_text
6
+
7
+ READ_LIMIT = 64 * 1024 * 1024
8
+ TOKEN_PATH = re.compile(r'(?:gh[pousr]_|github_pat_|sk-|glpat-|npm_|whsec_|rk_live_|sk_live_|hf_|xapp-|xox[baprs]-|SG\.|re_|AIza|AKIA|ASIA)[A-Za-z0-9_./+\-]{16,}')
9
+
10
+
11
+ def safe_path(path, env_hashes=frozenset(), internal_patterns=()):
12
+ raw = path.encode('utf-8', 'surrogateescape')
13
+ text = decoded_text(raw)
14
+ private = EMAIL.search(text) or LOCAL_PATH.search(text) or TOKEN_PATH.search(text)
15
+ if (private or hashlib.sha256(raw).digest() in env_hashes or any(re.search(p, text) for p in internal_patterns)
16
+ or any(ord(c) < 32 or 0xD800 <= ord(c) <= 0xDFFF for c in path) or len(path) > 500):
17
+ return '[redacted-path-sha256:' + hashlib.sha256(raw).hexdigest() + ']'
18
+ return path
@@ -0,0 +1,89 @@
1
+ """Same-tree, explicit project identities; never a repository-name allowlist."""
2
+ import json
3
+ from pathlib import Path
4
+ import re
5
+ import subprocess
6
+ from hygiene_text import decoded_text
7
+ from policy import READ_LIMIT
8
+ from reviewed import unique_fields
9
+
10
+ FIELDS = {'edition.json': 'id', 'sources.lock.json': 'edition', 'rer-project.json': 'projectId'}
11
+ TOOLCHAIN = {'id': 'runeditrun/edition-authoring', 'version': '1.1.0'}
12
+ VALIDATOR = Path(__file__).with_name('identity-recipe.mjs')
13
+
14
+
15
+ def document(data):
16
+ def reject_constant(value):
17
+ raise ValueError('non-JSON constant')
18
+ value = json.loads(data, object_pairs_hook=unique_fields, parse_constant=reject_constant)
19
+ if not isinstance(value, dict):
20
+ raise ValueError('invalid identity document')
21
+ return value
22
+
23
+
24
+ def schema_one(value):
25
+ return not isinstance(value, bool) and value == 1
26
+
27
+
28
+ def same_toolchain(value):
29
+ return isinstance(value, dict) and all(value.get(k) == v for k, v in TOOLCHAIN.items())
30
+
31
+
32
+ def ordinary_document(entry):
33
+ return bool(entry and entry['kind'] == 'blob' and entry['mode'] in ('100644', '100755')
34
+ and entry['size'] <= READ_LIMIT)
35
+
36
+
37
+ def tree_identity(history, root_entries):
38
+ """Validate actual nonrecursive root entries, including empty Git trees."""
39
+ roots = {e['path']: e for e in root_entries if e['path'] in FIELDS}
40
+ entry = roots.get('edition.json')
41
+ if entry is None:
42
+ return None, frozenset()
43
+
44
+ def read(entry):
45
+ if not ordinary_document(entry):
46
+ raise ValueError('unreadable identity document')
47
+ return history.blob(entry['oid'])
48
+
49
+ # An unreadable root cannot establish whether the edition opts in. It must
50
+ # not silently become an absent/legacy edition, even when Git omits it from
51
+ # the recursive file inventory. Ordinary legacy JSON keeps its old rules.
52
+ try:
53
+ data = read(entry)
54
+ except (ValueError, TypeError):
55
+ return None, frozenset({'edition.json'})
56
+ try:
57
+ recipe = document(data)
58
+ except (ValueError, TypeError, UnicodeError):
59
+ # Malformed opt-ins cannot become ordinary, permissive metadata. Other
60
+ # legacy malformed documents keep the existing scanner behaviour.
61
+ opted = re.search(r'\bidentityKind\b', decoded_text(data))
62
+ return None, frozenset({'edition.json'} if opted else ())
63
+ if 'identityKind' not in recipe:
64
+ return None, frozenset()
65
+ try:
66
+ if recipe['identityKind'] != 'project':
67
+ raise ValueError('unknown identity kind')
68
+ result = subprocess.run(['node', str(VALIDATOR)], input=data, capture_output=True, timeout=15)
69
+ canonical = result.stdout.decode('ascii')
70
+ if result.returncode or not re.fullmatch(r'[0-9a-f]{64}', canonical):
71
+ raise ValueError('invalid recipe')
72
+ except (OSError, ValueError, UnicodeError, subprocess.TimeoutExpired):
73
+ return None, frozenset({'edition.json'})
74
+ invalid = set()
75
+ for name in ('sources.lock.json', 'rer-project.json'):
76
+ if name not in roots:
77
+ continue
78
+ try:
79
+ value = document(read(roots[name]))
80
+ if value.get(FIELDS[name]) != recipe['id']:
81
+ raise ValueError('inconsistent project identity')
82
+ if name == 'sources.lock.json':
83
+ if not schema_one(value.get('schemaVersion')) or not same_toolchain(value.get('toolchain')) or value.get('manifest') != canonical:
84
+ raise ValueError('inconsistent source lock')
85
+ elif not schema_one(value.get('schema')):
86
+ raise ValueError('invalid project metadata')
87
+ except (ValueError, TypeError, UnicodeError):
88
+ invalid.add(name)
89
+ return (None if invalid else recipe['id']), frozenset(invalid)
@@ -0,0 +1,91 @@
1
+ """Recognize source declarations, never ordinary repository mentions."""
2
+ import json
3
+ import re
4
+ from github_refs import URL, FULL, normalize
5
+
6
+ MANIFESTS = ('edition.json', 'sources.lock.json')
7
+
8
+
9
+ def source_key(value):
10
+ if not isinstance(value, dict):
11
+ return None
12
+ repository = value.get('repository')
13
+ revision = value.get('revision', value.get('commit'))
14
+ if not isinstance(repository, str) or not isinstance(revision, str) or not re.fullmatch(r'[0-9a-f]{40}|[0-9a-f]{64}', revision):
15
+ return None
16
+ if '://' in repository and repository.split('://', 1)[0].lower() not in ('http', 'https', 'git', 'ssh', 'git+http', 'git+https', 'git+ssh'):
17
+ return None
18
+ match = URL.fullmatch(repository)
19
+ repo = normalize(match.group(1)) if match else normalize(repository) if FULL.fullmatch(repository) else None
20
+ return (repo, revision) if repo else None
21
+
22
+
23
+ def declarations(document):
24
+ if not isinstance(document, dict):
25
+ return
26
+ pending = [document.get('parent')]
27
+ components = document.get('components', [])
28
+ if isinstance(components, list):
29
+ pending.extend(c.get('source') for c in components if isinstance(c, dict))
30
+ while pending:
31
+ value = pending.pop()
32
+ if not isinstance(value, dict):
33
+ continue
34
+ if source_key(value):
35
+ yield value
36
+ pending.append(value.get('origin'))
37
+
38
+
39
+ def declared_keys(data):
40
+ try:
41
+ return {source_key(value) for value in declarations(json.loads(data))}
42
+ except (ValueError, TypeError):
43
+ return set()
44
+
45
+
46
+ def copy_record(document, declared):
47
+ """Canonical copy inventories bind the acquisition identity to declared pins."""
48
+ copies = document.get('copies')
49
+ if document.get('schemaVersion') != 1 or not isinstance(copies, list) or not copies:
50
+ return False
51
+ for copy in copies:
52
+ if not isinstance(copy, dict) or source_key(dict(repository=document.get('repository'), revision=copy.get('revision'))) not in declared:
53
+ return False
54
+ path, origin = copy.get('path'), copy.get('originPath')
55
+ if not all(isinstance(value, str) and value and not value.startswith('/') and '..' not in value.split('/') for value in (path, origin)):
56
+ return False
57
+ files, modes = copy.get('files'), copy.get('modes')
58
+ if not isinstance(files, dict) or not files or not isinstance(modes, dict) or files.keys() != modes.keys():
59
+ return False
60
+ if any(not (name == path or name.startswith(path + '/')) or not isinstance(value, str) or not re.fullmatch(r'[0-9a-f]{64}', value) or modes[name] not in ('100644', '100755') for name, value in files.items()):
61
+ return False
62
+ return True
63
+
64
+
65
+ def provenance_text(text, path, declared=frozenset()):
66
+ snapshot = re.fullmatch(r'\.edition/sources/[0-9a-f]{64}\.json', path)
67
+ copied = path.rsplit('/', 1)[-1].lower() in ('provenance.json', 'origin.json')
68
+ if path not in MANIFESTS and not snapshot and not copied and not path.endswith('.json'):
69
+ return text, False
70
+ try:
71
+ document = json.loads(text)
72
+ except ValueError:
73
+ return text, False
74
+ if not isinstance(document, dict):
75
+ return text, False
76
+ if copy_record(document, declared):
77
+ document['repository'] = ''
78
+ return json.dumps(document), True
79
+ candidates = list(declarations(document)) if path in MANIFESTS else []
80
+ if snapshot and isinstance(document.get('files'), dict) and source_key(document.get('origin')) in declared:
81
+ candidates.append(document['origin'])
82
+ hashes = document.get('files')
83
+ copies = document.get('copies')
84
+ mapped = isinstance(copies, dict) and copies and all(isinstance(k, str) and isinstance(v, str) and k and v for k, v in copies.items())
85
+ hashed = isinstance(hashes, dict) and hashes and all(isinstance(h, str) and re.fullmatch(r'[0-9a-f]{64}', h) for h in hashes.values())
86
+ selected = isinstance(document.get('path'), str) and bool(document['path'])
87
+ if copied and source_key(document) in declared and (mapped or hashed or selected):
88
+ candidates.append(document)
89
+ for value in candidates:
90
+ value['repository'] = ''
91
+ return (json.dumps(document) if candidates else text), bool(candidates)
@@ -0,0 +1,33 @@
1
+ """Git object IDs at declared module-provenance locations, never whole files."""
2
+ from json_identity import GIT_REVISION
3
+
4
+
5
+ def module_pins(path, document):
6
+ if not isinstance(document, dict):
7
+ return
8
+ if path == 'base/PROVENANCE.json':
9
+ maps = [(('moduleCommits',), document.get('moduleCommits'))]
10
+ elif path in ('edition.json', 'sources.lock.json'):
11
+ if type(document.get('schemaVersion')) is not int or document['schemaVersion'] != 1:
12
+ return
13
+ components = document.get('components')
14
+ if not isinstance(components, list):
15
+ return
16
+ maps = []
17
+ for index, component in enumerate(components):
18
+ source = component.get('source') if isinstance(component, dict) else None
19
+ if not isinstance(source, dict):
20
+ continue
21
+ suffix = () if source.get('kind') == 'git' else ('origin',) if source.get('kind') == 'local' else None
22
+ if suffix is None:
23
+ continue
24
+ origin = source if not suffix else source.get('origin')
25
+ if isinstance(origin, dict):
26
+ maps.append((('components', index, 'source', *suffix, 'moduleRevisions'), origin.get('moduleRevisions')))
27
+ else:
28
+ return
29
+ for pointer, pins in maps:
30
+ if isinstance(pins, dict):
31
+ for module, revision in pins.items():
32
+ if module and isinstance(revision, str) and GIT_REVISION.fullmatch(revision):
33
+ yield (*pointer, module)
@@ -0,0 +1,63 @@
1
+ """A public base is a branch of an explicitly named remote, never a local ref.
2
+
3
+ The declared base decides which history counts as already public, so it must
4
+ come from the remote itself: `<url>#<branch>` is fetched into an isolated
5
+ namespace and local branches, HEAD, tags and commit IDs are refused.
6
+ """
7
+ import os
8
+ import re
9
+ import subprocess
10
+
11
+ NAMESPACE = 'refs/publish-check/public/'
12
+ URL = re.compile(r'(?:(?:https|ssh|git|file)://[^\s/#]*/[^\s#]*|[A-Za-z0-9._-]+@[A-Za-z0-9.-]+:[^\s#]+|/[^\s#]*)')
13
+
14
+
15
+ class PublicBaseError(Exception):
16
+ """Carries a stable, value-free reason code."""
17
+
18
+ def __init__(self, code):
19
+ super().__init__(code)
20
+ self.code = code
21
+
22
+
23
+ def parse(spec, repo):
24
+ """Return (url, branch) or refuse."""
25
+ url, separator, branch = spec.rpartition('#')
26
+ if not separator or not url or not branch:
27
+ raise PublicBaseError('public-base.not-remote-branch')
28
+ if not URL.fullmatch(url) or url.startswith('-') or any(ord(c) < 32 for c in spec):
29
+ raise PublicBaseError('public-base.not-remote-url')
30
+ if branch.startswith(('-', 'refs/')) or branch == 'HEAD':
31
+ raise PublicBaseError('public-base.not-branch-name')
32
+ if not valid_ref('refs/heads/' + branch):
33
+ raise PublicBaseError('public-base.not-branch-name')
34
+ local = url[len('file://'):] if url.startswith('file://') else (url if url.startswith('/') else None)
35
+ if local and same_repository(local, repo):
36
+ raise PublicBaseError('public-base.is-candidate-repository')
37
+ return url, branch
38
+
39
+
40
+ def valid_ref(name):
41
+ try:
42
+ return subprocess.run(['git', 'check-ref-format', name], capture_output=True, timeout=10).returncode == 0
43
+ except (OSError, subprocess.TimeoutExpired):
44
+ return False
45
+
46
+
47
+ def common_dir(path, env):
48
+ result = subprocess.run(['git', '-C', path, 'rev-parse', '--path-format=absolute', '--git-common-dir'],
49
+ env=env, capture_output=True, timeout=10)
50
+ return os.path.realpath(os.fsdecode(result.stdout.strip())) if result.returncode == 0 else None
51
+
52
+
53
+ def same_repository(path, repo, env=None):
54
+ env = env or {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
55
+ try:
56
+ remote = common_dir(path, env)
57
+ return remote is not None and remote == common_dir(repo, env)
58
+ except (OSError, subprocess.TimeoutExpired):
59
+ raise PublicBaseError('public-base.fetch-failed') from None
60
+
61
+
62
+ def display(url):
63
+ return re.sub(r'^([a-z]+://)[^/@]*@', r'\1', url)
@@ -0,0 +1,80 @@
1
+ """Public-tree and message hygiene; findings never include matched values."""
2
+ from collections import Counter
3
+ import re
4
+ from github_refs import REPOSITORY_HOSTS, references
5
+ from destination import RULE as PENDING_DESTINATION
6
+ from hygiene_text import decoded_text
7
+ from provenance import provenance_text
8
+ from hygiene_config import EMAIL, LOCAL_PATH, public_email
9
+ from font_licence import without_required_notices
10
+ from trust_bundle import anchor_values
11
+ URI_AUTHORITY = re.compile(r'''(?i)(?a:(?P<scheme>[a-z][a-z0-9+.-]*:))?//(?P<authority>[^/?#\s<>"']+)''')
12
+
13
+
14
+ def email_userinfo(match):
15
+ userinfo, _, host = match['authority'].rpartition('@')
16
+ # Without a scheme, arbitrary //text can also be a source comment.
17
+ if not match['scheme'] and host.lower().split(':', 1)[0] not in REPOSITORY_HOSTS:
18
+ return match.group()
19
+ # Keep actual email-valued userinfo, including a decoded %40, while
20
+ # excluding the host delimiter in an ordinary transport username@host.
21
+ return userinfo
22
+
23
+
24
+ def private_emails(text, config):
25
+ # URL paths, queries and fragments remain visible to email inspection.
26
+ email_text = URI_AUTHORITY.sub(email_userinfo, without_required_notices(text))
27
+ email_text = re.sub(r'(?i)\b(?a:git@github\.com:)(?=[A-Za-z0-9])', '', email_text)
28
+ version = r'(?:v?[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9-]+)?|[a-f0-9]{40})(?![A-Za-z0-9_.-])'
29
+ email_text = re.sub(r'(?m)^\s*-?\s*uses:\s*[\"\']?[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[^\s\"\']+', '', email_text)
30
+ email_text = re.sub(r'(?<![A-Za-z0-9])(?:@[A-Za-z0-9_.-]+/)?[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)?@' + version, '', email_text)
31
+ return [match.group() for match in EMAIL.finditer(email_text) if not public_email(match.group(), config)]
32
+
33
+
34
+ def certificate_metadata(data, bundle, emails, config):
35
+ # Every match lies inside a verified trust-anchor subject value: none
36
+ # remains once those values are blanked, and each is one of theirs.
37
+ spans = anchor_values(data, bundle)
38
+ if not spans:
39
+ return False
40
+ blanked = bytearray(data)
41
+ for start, end in spans:
42
+ blanked[start:end] = b' ' * (end - start)
43
+ if private_emails(decoded_text(bytes(blanked)), config):
44
+ return False
45
+ inside = Counter(match.group() for start, end in spans for match in EMAIL.finditer(decoded_text(data[start:end])))
46
+ return not Counter(emails) - inside
47
+
48
+
49
+ def inspect(data, path, config, resolver):
50
+ return inspect_text(data, path, config, resolver)
51
+
52
+
53
+ def inspect_text(data, path, config, resolver):
54
+ # NUL removal covers UTF-16/32 ASCII content, matching the secret scanner.
55
+ text = decoded_text(data)
56
+ rules = set()
57
+ if LOCAL_PATH.search(text):
58
+ rules.add('hygiene.local-path')
59
+ if any(pattern.search(text) for pattern in config['internalReferencePatterns']):
60
+ rules.add('hygiene.internal-reference')
61
+ if '@' in text:
62
+ emails = private_emails(text, config)
63
+ bundle = config.get('trustBundles', {}).get(path)
64
+ if emails and not (bundle and certificate_metadata(data, bundle, emails, config)):
65
+ rules.add('hygiene.email')
66
+ ref_text, provenance = provenance_text(text, path, config.get('declaredSources', frozenset()))
67
+ for repository in references(ref_text, path, config.get('localPaths', frozenset()), config.get('projectIdentity')):
68
+ # A future source identity does not assert current GitHub visibility.
69
+ # Explicit release destinations still take the strict visibility path.
70
+ if (repository in config.get('intendedPublicRepositories', frozenset()) and
71
+ repository != config.get('pendingDestination')):
72
+ continue
73
+ state = resolver.resolve(repository)
74
+ if state == 'missing' and repository == config.get('pendingDestination'):
75
+ rules.add(PENDING_DESTINATION)
76
+ elif state in ('private', 'missing'):
77
+ rules.add('hygiene.github-' + state)
78
+ elif state != 'public':
79
+ rules.add('scan.github-incomplete')
80
+ return rules, provenance