@runeditrun/rer 0.0.0-stage → 0.1.1

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 (527) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -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 +182 -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 +158 -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 +117 -0
  71. package/global/launch/facts.mjs +94 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +63 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +106 -0
  77. package/global/launch/steps.mjs +90 -0
  78. package/global/launch/stop.mjs +89 -0
  79. package/global/launch/worker.mjs +62 -0
  80. package/global/list.mjs +59 -0
  81. package/global/main.mjs +53 -0
  82. package/global/package-info.mjs +38 -0
  83. package/global/registry.mjs +24 -0
  84. package/global/rer.mjs +29 -0
  85. package/global/update/archive.mjs +89 -0
  86. package/global/update/command.mjs +90 -0
  87. package/global/update/latest.mjs +6 -0
  88. package/global/update/notice.mjs +58 -0
  89. package/global/update/registry.mjs +47 -0
  90. package/global/version.mjs +29 -0
  91. package/install.sh +144 -0
  92. package/modules/ai/LICENSE +21 -0
  93. package/modules/ai/README.md +90 -0
  94. package/modules/ai/client.mjs +71 -0
  95. package/modules/ai/config.mjs +39 -0
  96. package/modules/ai/dev/index.d.mts +6 -0
  97. package/modules/ai/dev/index.mjs +64 -0
  98. package/modules/ai/dev/outbox.mjs +33 -0
  99. package/modules/ai/errors.mjs +28 -0
  100. package/modules/ai/fake/index.d.mts +3 -0
  101. package/modules/ai/fake/index.mjs +82 -0
  102. package/modules/ai/index.d.mts +10 -0
  103. package/modules/ai/index.mjs +3 -0
  104. package/modules/ai/package.json +6 -0
  105. package/modules/container/README.md +156 -0
  106. package/modules/container/adapters/d1.mjs +34 -0
  107. package/modules/container/adapters/egress.mjs +40 -0
  108. package/modules/container/adapters/jobs.mjs +47 -0
  109. package/modules/container/adapters/objects.mjs +84 -0
  110. package/modules/container/adapters/retained.mjs +14 -0
  111. package/modules/container/adapters/sqlite-error.mjs +7 -0
  112. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  113. package/modules/container/assets.mjs +14 -0
  114. package/modules/container/bindings.mjs +1 -0
  115. package/modules/container/boundary.mjs +38 -0
  116. package/modules/container/build.mjs +65 -0
  117. package/modules/container/deploy-input.mjs +17 -0
  118. package/modules/container/entrypoint.sh +5 -0
  119. package/modules/container/host.mjs +43 -0
  120. package/modules/container/ingress.mjs +23 -0
  121. package/modules/container/lock.mjs +11 -0
  122. package/modules/container/migrations.mjs +16 -0
  123. package/modules/container/node-engine.mjs +39 -0
  124. package/modules/container/portability-schema.mjs +14 -0
  125. package/modules/container/remote/deploy.py +76 -0
  126. package/modules/container/remote/files.py +275 -0
  127. package/modules/container/remote/resources.py +157 -0
  128. package/modules/container/runner.mjs +10 -0
  129. package/modules/container/ssh-plan.mjs +44 -0
  130. package/modules/container/ssh.mjs +63 -0
  131. package/modules/container/transfer.mjs +27 -0
  132. package/modules/container/worker-engine.mjs +32 -0
  133. package/modules/dependencies/README.md +211 -0
  134. package/modules/dependencies/approval.mjs +51 -0
  135. package/modules/dependencies/auth.mjs +40 -0
  136. package/modules/dependencies/authority.mjs +14 -0
  137. package/modules/dependencies/crypto.mjs +13 -0
  138. package/modules/dependencies/index.d.mts +23 -0
  139. package/modules/dependencies/index.mjs +113 -0
  140. package/modules/dependencies/logout.mjs +42 -0
  141. package/modules/dependencies/native.mjs +9 -0
  142. package/modules/dependencies/protocol.mjs +23 -0
  143. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  144. package/modules/dependencies/schema/dependencies.sql +15 -0
  145. package/modules/dependencies/store.mjs +19 -0
  146. package/modules/deploy-kit/README.md +308 -0
  147. package/modules/deploy-kit/artifact.mjs +47 -0
  148. package/modules/deploy-kit/build-modules.mjs +21 -0
  149. package/modules/deploy-kit/check.mjs +13 -0
  150. package/modules/deploy-kit/cli.mjs +50 -0
  151. package/modules/deploy-kit/container-config.mjs +8 -0
  152. package/modules/deploy-kit/default-stage.mjs +14 -0
  153. package/modules/deploy-kit/deploy.mjs +80 -0
  154. package/modules/deploy-kit/health-request.mjs +86 -0
  155. package/modules/deploy-kit/health.mjs +99 -0
  156. package/modules/deploy-kit/index.mjs +12 -0
  157. package/modules/deploy-kit/local-only.mjs +10 -0
  158. package/modules/deploy-kit/migrations.mjs +48 -0
  159. package/modules/deploy-kit/owner-cli.mjs +63 -0
  160. package/modules/deploy-kit/owner.mjs +297 -0
  161. package/modules/deploy-kit/prepare.mjs +62 -0
  162. package/modules/deploy-kit/provider.mjs +99 -0
  163. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  164. package/modules/deploy-kit/receipt.mjs +16 -0
  165. package/modules/deploy-kit/render.mjs +47 -0
  166. package/modules/deploy-kit/resources.mjs +106 -0
  167. package/modules/deploy-kit/service-grants.mjs +25 -0
  168. package/modules/deploy-kit/sql-safety.mjs +94 -0
  169. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  170. package/modules/deploy-kit/teardown.mjs +100 -0
  171. package/modules/deploy-kit/templates.mjs +31 -0
  172. package/modules/deploy-kit/validation.mjs +58 -0
  173. package/modules/deploy-kit/worker.mjs +105 -0
  174. package/modules/erasure/CONTRACT.md +90 -0
  175. package/modules/erasure/README.md +221 -0
  176. package/modules/erasure/audit.mjs +23 -0
  177. package/modules/erasure/blobs.mjs +45 -0
  178. package/modules/erasure/cascade.mjs +91 -0
  179. package/modules/erasure/completion.mjs +62 -0
  180. package/modules/erasure/declaration.mjs +51 -0
  181. package/modules/erasure/fence.mjs +19 -0
  182. package/modules/erasure/index.d.mts +62 -0
  183. package/modules/erasure/index.mjs +85 -0
  184. package/modules/erasure/order.mjs +56 -0
  185. package/modules/erasure/plan.mjs +121 -0
  186. package/modules/erasure/rate.mjs +57 -0
  187. package/modules/erasure/receipt.mjs +32 -0
  188. package/modules/erasure/root.mjs +21 -0
  189. package/modules/erasure/schema/erasure-rates.sql +5 -0
  190. package/modules/erasure/schema/erasure.sql +27 -0
  191. package/modules/erasure/scope.mjs +29 -0
  192. package/modules/erasure/sql.mjs +23 -0
  193. package/modules/erasure/verification.mjs +69 -0
  194. package/modules/integrations/core/CONTRACT.md +50 -0
  195. package/modules/integrations/core/README.md +27 -0
  196. package/modules/integrations/core/crypto.ts +19 -0
  197. package/modules/integrations/core/database.ts +21 -0
  198. package/modules/integrations/core/delivery.ts +97 -0
  199. package/modules/integrations/core/destination.ts +13 -0
  200. package/modules/integrations/core/index.ts +7 -0
  201. package/modules/integrations/core/node-transport.ts +72 -0
  202. package/modules/integrations/core/public-address.ts +57 -0
  203. package/modules/integrations/core/repository.ts +74 -0
  204. package/modules/integrations/core/schema.sql +36 -0
  205. package/modules/integrations/core/sqlite.ts +20 -0
  206. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  207. package/modules/integrations/core/tokens.ts +71 -0
  208. package/modules/integrations/core/types.ts +52 -0
  209. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  210. package/modules/integrations/core/webhook.ts +73 -0
  211. package/modules/job-queue/README.md +173 -0
  212. package/modules/job-queue/cli.mjs +64 -0
  213. package/modules/job-queue/command.mjs +64 -0
  214. package/modules/job-queue/config.mjs +48 -0
  215. package/modules/job-queue/identity.mjs +48 -0
  216. package/modules/job-queue/index.mjs +135 -0
  217. package/modules/job-queue/membership.mjs +30 -0
  218. package/modules/job-queue/module.json +10 -0
  219. package/modules/job-queue/policy.mjs +29 -0
  220. package/modules/job-queue/processes.mjs +80 -0
  221. package/modules/job-queue/resources.mjs +37 -0
  222. package/modules/job-queue/storage.mjs +64 -0
  223. package/modules/job-queue/supervisor.mjs +30 -0
  224. package/modules/job-queue/waiting.mjs +16 -0
  225. package/modules/mail/LICENSE +21 -0
  226. package/modules/mail/README.md +336 -0
  227. package/modules/mail/doctor.mjs +81 -0
  228. package/modules/mail/index.mjs +8 -0
  229. package/modules/mail/live-proof/README.md +113 -0
  230. package/modules/mail/live-proof/run.mjs +55 -0
  231. package/modules/mail/package.json +19 -0
  232. package/modules/mail/pnpm-lock.yaml +537 -0
  233. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  234. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  235. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  236. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  237. package/modules/mail/src/adapters/messaging/README.md +92 -0
  238. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  239. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  240. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  241. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  242. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  243. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  244. package/modules/mail/src/adapters/resend/README.md +120 -0
  245. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  246. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  247. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  248. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  249. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  250. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  251. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  252. package/modules/mail/src/core/errors.mjs +9 -0
  253. package/modules/mail/src/core/transport.mjs +33 -0
  254. package/modules/mail/src/core/validation.mjs +172 -0
  255. package/modules/mail/src/development/index.d.mts +37 -0
  256. package/modules/mail/src/development/index.mjs +35 -0
  257. package/modules/mail/src/development/outbox.mjs +78 -0
  258. package/modules/mail/src/development/schema.sql +9 -0
  259. package/modules/mail/src/development/viewer-html.mjs +42 -0
  260. package/modules/mail/src/development/viewer.mjs +65 -0
  261. package/modules/mail/src/doctor/README.md +107 -0
  262. package/modules/mail/src/doctor/common.mjs +64 -0
  263. package/modules/mail/src/doctor/dns.mjs +102 -0
  264. package/modules/mail/src/doctor/index.mjs +28 -0
  265. package/modules/mail/src/doctor/probe.mjs +66 -0
  266. package/modules/mail/src/doctor/receive.mjs +81 -0
  267. package/modules/mail/src/doctor/records.mjs +45 -0
  268. package/modules/mail/src/events/README.md +268 -0
  269. package/modules/mail/src/events/d1-schema.sql +25 -0
  270. package/modules/mail/src/events/d1-storage.mjs +76 -0
  271. package/modules/mail/src/events/index.mjs +3 -0
  272. package/modules/mail/src/events/intake.mjs +201 -0
  273. package/modules/mail/src/events/storage.mjs +52 -0
  274. package/modules/mail/src/events/verify.mjs +92 -0
  275. package/modules/mail/src/inbound/README.md +137 -0
  276. package/modules/mail/src/inbound/bytes.mjs +91 -0
  277. package/modules/mail/src/inbound/headers.mjs +143 -0
  278. package/modules/mail/src/inbound/index.mjs +29 -0
  279. package/modules/mail/src/inbound/mime.mjs +36 -0
  280. package/modules/mail/src/inbound/multipart.mjs +27 -0
  281. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  282. package/modules/mail/src/inbound/resend.mjs +54 -0
  283. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  284. package/modules/mail/src/sender.mjs +33 -0
  285. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  286. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  287. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  288. package/modules/operator-auth/README.md +599 -0
  289. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  290. package/modules/operator-auth/accounts/admin.mjs +159 -0
  291. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  292. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  293. package/modules/operator-auth/accounts/index.d.mts +9 -0
  294. package/modules/operator-auth/accounts/index.mjs +9 -0
  295. package/modules/operator-auth/accounts/native.d.mts +38 -0
  296. package/modules/operator-auth/accounts/native.mjs +171 -0
  297. package/modules/operator-auth/accounts/oa.mjs +90 -0
  298. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  299. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  300. package/modules/operator-auth/accounts/roles.mjs +26 -0
  301. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  302. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  303. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  304. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  305. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  306. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  307. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  308. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  309. package/modules/operator-auth/accounts/store.mjs +246 -0
  310. package/modules/operator-auth/branding.mjs +66 -0
  311. package/modules/operator-auth/config.mjs +35 -0
  312. package/modules/operator-auth/conformance/README.md +81 -0
  313. package/modules/operator-auth/conformance/index.d.mts +43 -0
  314. package/modules/operator-auth/conformance/index.mjs +300 -0
  315. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  316. package/modules/operator-auth/crypto.mjs +17 -0
  317. package/modules/operator-auth/dev/badge.mjs +62 -0
  318. package/modules/operator-auth/dev/index.d.mts +14 -0
  319. package/modules/operator-auth/dev/index.mjs +137 -0
  320. package/modules/operator-auth/dev/personas.mjs +17 -0
  321. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  322. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  323. package/modules/operator-auth/dev-admission.d.mts +4 -0
  324. package/modules/operator-auth/dev-admission.mjs +10 -0
  325. package/modules/operator-auth/dev-gate.mjs +86 -0
  326. package/modules/operator-auth/docs/README.md +78 -0
  327. package/modules/operator-auth/docs/native-accounts.md +313 -0
  328. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  329. package/modules/operator-auth/docs/vite-spa.md +144 -0
  330. package/modules/operator-auth/errors.mjs +9 -0
  331. package/modules/operator-auth/http.mjs +42 -0
  332. package/modules/operator-auth/identity/admission.mjs +105 -0
  333. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  334. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  335. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  336. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  337. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  338. package/modules/operator-auth/identity/index.d.mts +7 -0
  339. package/modules/operator-auth/identity/index.mjs +5 -0
  340. package/modules/operator-auth/identity/oidc/README.md +171 -0
  341. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  342. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  343. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  344. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  345. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  346. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  347. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  348. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  349. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  350. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  351. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  352. package/modules/operator-auth/identity/principal.mjs +28 -0
  353. package/modules/operator-auth/index.d.mts +223 -0
  354. package/modules/operator-auth/index.mjs +11 -0
  355. package/modules/operator-auth/input.mjs +55 -0
  356. package/modules/operator-auth/local-cli.mjs +16 -0
  357. package/modules/operator-auth/page.mjs +34 -0
  358. package/modules/operator-auth/policy.mjs +20 -0
  359. package/modules/operator-auth/routes.mjs +91 -0
  360. package/modules/operator-auth/service.mjs +152 -0
  361. package/modules/operator-auth/signin/index.mjs +67 -0
  362. package/modules/owner-claim/README.md +137 -0
  363. package/modules/owner-claim/idp.mjs +40 -0
  364. package/modules/owner-claim/index.d.mts +27 -0
  365. package/modules/owner-claim/index.mjs +39 -0
  366. package/modules/owner-claim/page.mjs +11 -0
  367. package/modules/owner-claim/schema/idp.sql +4 -0
  368. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  369. package/modules/owner-claim/transaction.mjs +87 -0
  370. package/modules/owner-claim/verify.mjs +80 -0
  371. package/modules/portability/README.md +444 -0
  372. package/modules/portability/bridge.mjs +102 -0
  373. package/modules/portability/catalogue.mjs +118 -0
  374. package/modules/portability/cli-options.mjs +38 -0
  375. package/modules/portability/cli.mjs +56 -0
  376. package/modules/portability/copies.mjs +68 -0
  377. package/modules/portability/dev-data.mjs +32 -0
  378. package/modules/portability/diff.mjs +33 -0
  379. package/modules/portability/export.mjs +129 -0
  380. package/modules/portability/files.mjs +65 -0
  381. package/modules/portability/format.mjs +122 -0
  382. package/modules/portability/generated.mjs +60 -0
  383. package/modules/portability/hash.mjs +79 -0
  384. package/modules/portability/http.mjs +42 -0
  385. package/modules/portability/import.mjs +185 -0
  386. package/modules/portability/index.d.mts +58 -0
  387. package/modules/portability/index.mjs +22 -0
  388. package/modules/portability/journal.mjs +49 -0
  389. package/modules/portability/legacy-files.mjs +43 -0
  390. package/modules/portability/legacy-safety.mjs +27 -0
  391. package/modules/portability/lines.mjs +36 -0
  392. package/modules/portability/migration-history.mjs +18 -0
  393. package/modules/portability/migration-statements.mjs +23 -0
  394. package/modules/portability/payloads.mjs +52 -0
  395. package/modules/portability/provider-data.mjs +25 -0
  396. package/modules/portability/restore-safety.mjs +30 -0
  397. package/modules/portability/schema/portability.sql +25 -0
  398. package/modules/portability/storage.mjs +49 -0
  399. package/modules/release/README.md +212 -0
  400. package/modules/release/catalogue.mjs +79 -0
  401. package/modules/release/export.mjs +51 -0
  402. package/modules/release/produce.mjs +136 -0
  403. package/modules/release/public-child-transport-state.cjs +29 -0
  404. package/modules/release/public-child.mjs +71 -0
  405. package/modules/release/public-git.mjs +21 -0
  406. package/modules/release/publication-scan.mjs +22 -0
  407. package/modules/release/publish-handoff.mjs +37 -0
  408. package/modules/release/source.mjs +71 -0
  409. package/modules/release/test-summary.mjs +50 -0
  410. package/onboarding/README.md +163 -0
  411. package/onboarding/activity.mjs +31 -0
  412. package/onboarding/adapter-auth.mjs +88 -0
  413. package/onboarding/adapter-hosting.mjs +35 -0
  414. package/onboarding/adapter-labels.mjs +13 -0
  415. package/onboarding/adapter-settings.mjs +129 -0
  416. package/onboarding/adapter.mjs +238 -0
  417. package/onboarding/agent-handoff.mjs +9 -0
  418. package/onboarding/app-processes.mjs +21 -0
  419. package/onboarding/archive-output.d.mts +3 -0
  420. package/onboarding/archive-output.mjs +54 -0
  421. package/onboarding/command-custody.mjs +24 -0
  422. package/onboarding/command-recovery.mjs +8 -0
  423. package/onboarding/commands.mjs +166 -0
  424. package/onboarding/dependency-approval.mjs +36 -0
  425. package/onboarding/dev-identity.mjs +23 -0
  426. package/onboarding/email.mjs +24 -0
  427. package/onboarding/failures.mjs +42 -0
  428. package/onboarding/input-limits.mjs +7 -0
  429. package/onboarding/installer-inputs.mjs +24 -0
  430. package/onboarding/launcher.mjs +34 -0
  431. package/onboarding/local/build-files.mjs +64 -0
  432. package/onboarding/local/build.mjs +49 -0
  433. package/onboarding/local/environment.mjs +30 -0
  434. package/onboarding/local/files.mjs +35 -0
  435. package/onboarding/local/operation.mjs +166 -0
  436. package/onboarding/local/owner.mjs +34 -0
  437. package/onboarding/local/process-table.mjs +114 -0
  438. package/onboarding/local/recovery.mjs +79 -0
  439. package/onboarding/local/state.mjs +31 -0
  440. package/onboarding/metadata.mjs +7 -0
  441. package/onboarding/operator-install.mjs +45 -0
  442. package/onboarding/operator-installer.mjs +39 -0
  443. package/onboarding/operator-prerequisites.mjs +27 -0
  444. package/onboarding/package.json +6 -0
  445. package/onboarding/prefill.mjs +16 -0
  446. package/onboarding/private-files.mjs +97 -0
  447. package/onboarding/private.mjs +35 -0
  448. package/onboarding/process-cleanup.mjs +134 -0
  449. package/onboarding/process-diagnostics.mjs +91 -0
  450. package/onboarding/process-scope.mjs +118 -0
  451. package/onboarding/process-table.mjs +118 -0
  452. package/onboarding/process-tree.mjs +36 -0
  453. package/onboarding/production-modules.mjs +22 -0
  454. package/onboarding/production-options.mjs +40 -0
  455. package/onboarding/production-plan.mjs +8 -0
  456. package/onboarding/production-runtime.mjs +30 -0
  457. package/onboarding/project-vite.mjs +14 -0
  458. package/onboarding/providers.mjs +289 -0
  459. package/onboarding/queue.mjs +53 -0
  460. package/onboarding/queued-entry.mjs +34 -0
  461. package/onboarding/readiness.mjs +59 -0
  462. package/onboarding/redaction-jwt.mjs +32 -0
  463. package/onboarding/redaction.mjs +298 -0
  464. package/onboarding/schema/SETUP.schema.json +514 -0
  465. package/onboarding/schema/dependencies.mjs +35 -0
  466. package/onboarding/schema/json-schema.mjs +202 -0
  467. package/onboarding/schema/setup-validation.mjs +325 -0
  468. package/onboarding/secrets.mjs +21 -0
  469. package/onboarding/server.mjs +188 -0
  470. package/onboarding/setup-manifest.mjs +116 -0
  471. package/onboarding/state.mjs +46 -0
  472. package/onboarding/theme-files.mjs +37 -0
  473. package/onboarding/theme.mjs +118 -0
  474. package/onboarding/ui/activity.js +98 -0
  475. package/onboarding/ui/address-client.mjs +33 -0
  476. package/onboarding/ui/app.js +377 -0
  477. package/onboarding/ui/index.html +1 -0
  478. package/onboarding/ui/model-core.mjs +127 -0
  479. package/onboarding/ui/model.mjs +5 -0
  480. package/onboarding/ui/style.css +63 -0
  481. package/onboarding/ui/theme.css +37 -0
  482. package/onboarding/ui/workspace-art.js +30 -0
  483. package/onboarding/wrangler-auth.mjs +43 -0
  484. package/operations/README.md +246 -0
  485. package/operations/operations.mjs +168 -0
  486. package/operations/project-operations.mjs +157 -0
  487. package/package.json +11 -4
  488. package/scripts/publish-check/README.md +72 -0
  489. package/scripts/publish-check/checksum_maps.py +52 -0
  490. package/scripts/publish-check/cli.py +34 -0
  491. package/scripts/publish-check/command_arguments.py +51 -0
  492. package/scripts/publish-check/command_shell_quotes.py +41 -0
  493. package/scripts/publish-check/command_source_quotes.py +120 -0
  494. package/scripts/publish-check/content_identities.py +250 -0
  495. package/scripts/publish-check/copy_identities.py +41 -0
  496. package/scripts/publish-check/der.py +61 -0
  497. package/scripts/publish-check/destination.py +117 -0
  498. package/scripts/publish-check/env_values.py +74 -0
  499. package/scripts/publish-check/font_licence.py +23 -0
  500. package/scripts/publish-check/git_history.py +115 -0
  501. package/scripts/publish-check/github_refs.py +217 -0
  502. package/scripts/publish-check/gitleaks.py +72 -0
  503. package/scripts/publish-check/gitleaks.toml +40 -0
  504. package/scripts/publish-check/history_scan.py +176 -0
  505. package/scripts/publish-check/hit_location.py +18 -0
  506. package/scripts/publish-check/hygiene_config.py +42 -0
  507. package/scripts/publish-check/hygiene_text.py +11 -0
  508. package/scripts/publish-check/identity-recipe.mjs +54 -0
  509. package/scripts/publish-check/json_identity.py +79 -0
  510. package/scripts/publish-check/license-exceptions.json +11 -0
  511. package/scripts/publish-check/licenses.py +49 -0
  512. package/scripts/publish-check/policy.py +18 -0
  513. package/scripts/publish-check/project_identity.py +89 -0
  514. package/scripts/publish-check/provenance.py +91 -0
  515. package/scripts/publish-check/provenance_pins.py +33 -0
  516. package/scripts/publish-check/public_base.py +63 -0
  517. package/scripts/publish-check/public_hygiene.py +80 -0
  518. package/scripts/publish-check/publication-config.json +12 -0
  519. package/scripts/publish-check/publication_config.py +34 -0
  520. package/scripts/publish-check/report.py +52 -0
  521. package/scripts/publish-check/repository_commands.py +75 -0
  522. package/scripts/publish-check/repository_intent.py +52 -0
  523. package/scripts/publish-check/reviewed.py +41 -0
  524. package/scripts/publish-check/source_hashes.mjs +6 -0
  525. package/scripts/publish-check/tree_scan.py +88 -0
  526. package/scripts/publish-check/trust_bundle.py +87 -0
  527. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "intendedPublicRepositories": [
4
+ { "repository": "just-every/cradle", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
5
+ { "repository": "just-every/penny", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
6
+ { "repository": "just-every/mango", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
7
+ { "repository": "just-every/lamp", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
8
+ { "repository": "just-every/dial", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
9
+ { "repository": "just-every/orbit", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
10
+ { "repository": "just-every/uptime-monitor", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." }
11
+ ]
12
+ }
@@ -0,0 +1,34 @@
1
+ """Exact owner-declared future public identities, maintained by Base only."""
2
+ import json
3
+ from pathlib import Path
4
+ from github_refs import FULL
5
+
6
+
7
+ def unique_object(pairs):
8
+ result = {}
9
+ for key, value in pairs:
10
+ if key in result:
11
+ raise ValueError('duplicate publication configuration key')
12
+ result[key] = value
13
+ return result
14
+
15
+
16
+ def intended_public_repositories():
17
+ path = Path(__file__).with_name('publication-config.json')
18
+ document = json.loads(path.read_bytes(), object_pairs_hook=unique_object)
19
+ if (not isinstance(document, dict) or
20
+ set(document) != {'schemaVersion', 'intendedPublicRepositories'} or
21
+ document['schemaVersion'] != 1 or
22
+ not isinstance(document['intendedPublicRepositories'], list)):
23
+ raise ValueError('invalid Base publication configuration')
24
+ repositories = set()
25
+ for entry in document['intendedPublicRepositories']:
26
+ if not isinstance(entry, dict) or set(entry) != {'repository', 'reason'}:
27
+ raise ValueError('invalid intended public repository')
28
+ repository, reason = entry['repository'], entry['reason']
29
+ if (not isinstance(repository, str) or not FULL.fullmatch(repository) or
30
+ repository != repository.lower() or repository in repositories or
31
+ not isinstance(reason, str) or not reason.strip()):
32
+ raise ValueError('invalid intended public repository')
33
+ repositories.add(repository)
34
+ return frozenset(repositories)
@@ -0,0 +1,52 @@
1
+ """Caller-compatible, value-free publication reports."""
2
+ from policy import safe_path
3
+ from reviewed import FORBIDDEN
4
+
5
+
6
+ def new_report(public_base=False):
7
+ return dict(schema='rer.publish-check/v3', status='INCOMPLETE', complete=False, source=None,
8
+ scanner=dict(name='gitleaks', version='8.30.1'),
9
+ history=dict(scope='selected-and-public-base' if public_base else 'all-local-refs-and-selected-commit',
10
+ commits=0, trees=0, blobs=0, commitMessages=0, tagMessages=0),
11
+ findings=[], preExisting=[], warnings=[], fixtures=[], reviewed=[], info=[],
12
+ guarantees=dict(historyScanned=False, visualRecognition=False, environmentChecked=False))
13
+
14
+
15
+ class Findings:
16
+ def __init__(self, report, reviewed, env_hashes):
17
+ self.report, self.reviewed, self.env_hashes = report, reviewed, env_hashes
18
+ self.seen = {}
19
+
20
+ def add(self, rule, path, commit=None, oid=None, scope='newly-public', bucket=None, scanner_rule=None):
21
+ bucket = bucket or ('preExisting' if scope == 'pre-existing' and rule != 'secret.env-match' else 'findings')
22
+ if rule in ('pending-public-destination', 'provenance.declared-source'):
23
+ bucket = 'info'
24
+ if (rule, path, oid) in self.reviewed and bucket != 'info':
25
+ if rule in FORBIDDEN:
26
+ self.add('reviewed.forbidden', path, commit, oid, scope)
27
+ else:
28
+ bucket = 'reviewed'
29
+ key = (bucket, rule, path, oid, scanner_rule)
30
+ if key in self.seen:
31
+ item = self.seen[key]
32
+ if commit not in item['commits']:
33
+ item['commits'].append(commit)
34
+ if scope not in item['scopes']:
35
+ item['scopes'].append(scope)
36
+ return
37
+ item = dict(rule=rule, path=safe_path(path, self.env_hashes), commit=commit, commits=[commit], scope=scope, scopes=[scope])
38
+ if oid:
39
+ item['blob'] = oid
40
+ if scanner_rule:
41
+ item['scannerRule'] = scanner_rule
42
+ if bucket == 'info':
43
+ item['severity'] = 'INFO'
44
+ self.seen[key] = item
45
+ self.report[bucket].append(item)
46
+
47
+
48
+ def finish(report):
49
+ report['complete'] = not any(f['rule'].startswith('scan.') for bucket in ('findings', 'preExisting') for f in report[bucket])
50
+ report['guarantees']['historyScanned'] = report['complete']
51
+ report['status'] = 'INCOMPLETE' if not report['complete'] else 'FAIL' if report['findings'] else 'PASS'
52
+ return report
@@ -0,0 +1,75 @@
1
+ """Bound shell command arguments by their surrounding source/prose context."""
2
+ import re
3
+ from command_arguments import arguments
4
+ from command_source_quotes import SourceQuotes
5
+
6
+ COMMAND = re.compile(r'(?i)(?<![\w-])(?:git[ \t]+clone|gh[ \t]+repo[ \t]+(?:clone|view|fork))[ \t]+')
7
+ QUOTES = re.compile(r'`+|\"{3}|\'{3}|[\"\']')
8
+
9
+
10
+ def enclosing_quotes(text, stops):
11
+ """Walk quote scopes once, yielding the innermost scope at each command."""
12
+ delimiters, index = [], 0
13
+ source_quotes = SourceQuotes(text)
14
+ for stop in stops:
15
+ while index < stop:
16
+ if text[index] == '\\':
17
+ index += 2
18
+ continue
19
+ delimiter, opened = delimiters[-1] if delimiters else (None, 0)
20
+ if delimiter:
21
+ if text[index] == '\n' and delimiter in ("'", '"'):
22
+ delimiters.pop()
23
+ index += 1
24
+ continue
25
+ if text.startswith(delimiter, index):
26
+ index += len(delimiter)
27
+ delimiters.pop()
28
+ continue
29
+ if not delimiter.startswith('`'):
30
+ index += 1
31
+ continue
32
+ # Inline code can contain source strings. The innermost source
33
+ # quote bounds its command before another argument or funding URL.
34
+ match = QUOTES.match(text, index)
35
+ # An already-open quote closes above, even when it follows a word.
36
+ apostrophe = match and match[0] == "'" and index and text[index - 1].isalnum()
37
+ if apostrophe:
38
+ start = opened + len(delimiter) if delimiter else 0
39
+ end = context_end(text, start, delimiter) if delimiter else len(text)
40
+ apostrophe = not source_quotes.contains(index, start, end)
41
+ if match and not apostrophe:
42
+ delimiters.append((match[0], index))
43
+ index = match.end()
44
+ else:
45
+ index += 1
46
+ yield delimiters[-1][0] if delimiters else None
47
+
48
+
49
+ def context_end(text, start, delimiter, stop=None):
50
+ stop = len(text) if stop is None else stop
51
+ if delimiter:
52
+ index = start
53
+ while index < stop:
54
+ if text[index] == '\\':
55
+ index += 2
56
+ elif text.startswith(delimiter, index):
57
+ return index
58
+ else:
59
+ index += 1
60
+ return stop
61
+
62
+
63
+ def commands(text):
64
+ """Retain complete arguments even if later surrounding prose is malformed."""
65
+ text = text.replace('\\\n', ' ')
66
+ matches = list(COMMAND.finditer(text))
67
+ scopes = enclosing_quotes(text, (match.start() for match in matches))
68
+ for match, delimiter in zip(matches, scopes):
69
+ line_end = text.find('\n', match.end())
70
+ if line_end < 0:
71
+ line_end = len(text)
72
+ end = context_end(text, match.end(), delimiter, line_end)
73
+ # Options may name reference repositories too. Shell separators
74
+ # and the outer text delimiter bound their repository intent.
75
+ yield ' '.join(arguments(text[match.end():end]))
@@ -0,0 +1,52 @@
1
+ """Repository declaration scopes independent of source language/extension."""
2
+ import re
3
+ from repository_commands import commands
4
+
5
+ FIELD = r'''["']?(?:repository|repo|github|sourceRepository)["']?'''
6
+ OBJECT = re.compile(r'(?i)(?<![\w$])' + FIELD + r'\s*[:=]\s*(?P<open>\{)')
7
+ TABLE = re.compile(r'''(?m)^\s*\[\[?\s*(?P<name>[^]\n]+)\]\]?[^\n]*''')
8
+ DECLARATION = re.compile(r'(?im)^(?P<indent>[ \t]*)(?:-\s*)?' + FIELD + r'[ \t]*:[ \t]*(?P<value>[^\n]*)')
9
+
10
+
11
+ def declarations(text):
12
+ for match in OBJECT.finditer(text):
13
+ start = match.start('open')
14
+ nesting, quote, escaped = 0, None, False
15
+ for end in range(start, len(text)):
16
+ char = text[end]
17
+ if quote:
18
+ if escaped:
19
+ escaped = False
20
+ elif char == '\\':
21
+ escaped = True
22
+ elif char == quote:
23
+ quote = None
24
+ elif char in ('"', "'", '`'):
25
+ quote = char
26
+ elif char == '{':
27
+ nesting += 1
28
+ elif char == '}':
29
+ nesting -= 1
30
+ if nesting == 0:
31
+ yield text[start:end + 1]
32
+ break
33
+ tables = list(TABLE.finditer(text))
34
+ for index, match in enumerate(tables):
35
+ names = [part.strip(' \t\'"').lower() for part in match['name'].split('.')]
36
+ if any(name in ('repository', 'repo', 'github', 'sourcerepository') for name in names):
37
+ end = tables[index + 1].start() if index + 1 < len(tables) else len(text)
38
+ yield text[match.end():end]
39
+ for match in DECLARATION.finditer(text):
40
+ if match['value'].lstrip().startswith('{'):
41
+ # Inline object boundaries were handled above; a sibling on the
42
+ # same line does not belong to the repository object.
43
+ continue
44
+ declaration = [match['value']]
45
+ indent = len(match['indent'].expandtabs())
46
+ for line in text[match.end():].splitlines()[1:]:
47
+ if not line.strip():
48
+ continue
49
+ if len(line.expandtabs()) - len(line.expandtabs().lstrip()) <= indent:
50
+ break
51
+ declaration.append(line)
52
+ yield '\n'.join(declaration)
@@ -0,0 +1,41 @@
1
+ """Exact committed finding identities; provider tokens and known environment values cannot be waived."""
2
+ import json
3
+ import re
4
+ from datetime import date, datetime, timezone
5
+ from hygiene_config import RULES
6
+
7
+ REVIEWED = '.publish-check/reviewed.json'
8
+ SECRET_RULES = {'secret.provider-token', 'secret.assignment', 'secret.private-key', 'secret.jwt',
9
+ 'secret.url-credentials', 'secret.authorization', 'secret.shaped-file'}
10
+ FORBIDDEN = {'secret.provider-token', 'secret.env-match', 'license.missing'}
11
+
12
+
13
+ def unique_fields(pairs):
14
+ result = {}
15
+ for key, value in pairs:
16
+ if key in result:
17
+ raise ValueError('duplicate object field')
18
+ result[key] = value
19
+ return result
20
+
21
+
22
+ def load_reviewed(data):
23
+ document = json.loads(data, object_pairs_hook=unique_fields)
24
+ if not isinstance(document, dict) or document.get('schemaVersion') != 1 or not isinstance(document.get('findings'), list):
25
+ raise ValueError('invalid reviewed document')
26
+ result = {}
27
+ for entry in document['findings']:
28
+ fields = {'rule', 'path', 'blob', 'reason', 'reviewer', 'date'}
29
+ if not isinstance(entry, dict) or set(entry) not in (fields, fields | {'fixtureValuesSha256'}, fields | {'containedFixtures'}) or entry['rule'] not in RULES | SECRET_RULES | FORBIDDEN | {'license.holder'}:
30
+ raise ValueError('invalid reviewed entry')
31
+ if not all(isinstance(entry[k], str) and entry[k].strip() for k in fields):
32
+ raise ValueError('invalid reviewed entry')
33
+ if not re.fullmatch(r'[0-9a-f]{40}|[0-9a-f]{64}', entry['blob']):
34
+ raise ValueError('invalid reviewed identity')
35
+ if date.fromisoformat(entry['date']) > datetime.now(timezone.utc).date():
36
+ raise ValueError('invalid review date')
37
+ key = (entry['rule'], entry['path'], entry['blob'])
38
+ # Older exports may repeat the same blob identity for distinct contained
39
+ # fixtures. Those fields no longer affect scanning; keep the review.
40
+ result[key] = entry
41
+ return result
@@ -0,0 +1,6 @@
1
+ // Reuse the maintained Source serializer; never approximate JSON.stringify in
2
+ // another language. Input and output stay on private subprocess pipes.
3
+ import fs from 'node:fs';
4
+ import {hash, json} from '../../edition-authoring/files.mjs';
5
+ const values = JSON.parse(fs.readFileSync(0, 'utf8'));
6
+ process.stdout.write(JSON.stringify(values.map(value => hash(json(value)))));
@@ -0,0 +1,88 @@
1
+ """Text hygiene and licence policy on immutable Git trees and present files."""
2
+ import hashlib
3
+ import os
4
+ from pathlib import Path
5
+ import re
6
+ import stat
7
+ from public_hygiene import inspect
8
+ from provenance import MANIFESTS, declared_keys
9
+ from project_identity import tree_identity
10
+ from licenses import license_rules
11
+ from git_history import InspectionError
12
+ from trust_bundle import bundles
13
+
14
+ DEPENDENCY_DIRS = {'.git', 'node_modules'}
15
+
16
+
17
+ def environment_match(data, hashes):
18
+ # Known values are compared in memory. No value or digest enters a report.
19
+ candidates = [data, *re.findall(rb'[A-Za-z0-9_./+\-]{8,}(?:={1,2})?', data)]
20
+ return any(hashlib.sha256(value).digest() in hashes for value in candidates)
21
+
22
+
23
+ def context(history, commit, config):
24
+ entries = history.entries(commit)
25
+ local = set()
26
+ for entry in entries:
27
+ parts = entry['path'].split('/')
28
+ local.update('/'.join(parts[:n]) for n in range(1, len(parts) + 1))
29
+ declared = set()
30
+ for entry in history.root_entries(commit):
31
+ if entry['path'] in MANIFESTS and entry['kind'] == 'blob':
32
+ declared.update(declared_keys(history.blob(entry['oid'])))
33
+ identity, invalid = tree_identity(history, history.root_entries(commit))
34
+ blobs = {entry['path']: entry['oid'] for entry in entries if entry['kind'] == 'blob'}
35
+ trust = bundles(blobs, lambda path: history.blob(blobs[path]))
36
+ return {**config, 'localPaths': frozenset(local), 'declaredSources': frozenset(declared), 'projectIdentity': identity,
37
+ 'trustBundles': trust}, invalid
38
+
39
+
40
+ def text_rules(data, path, config, resolver, env_hashes):
41
+ rules, provenance = inspect(data, path, config, resolver)
42
+ path_rules, _ = inspect(path.encode('utf-8', 'surrogateescape'), '@path', config, resolver)
43
+ rules.update(path_rules)
44
+ if env_hashes and (environment_match(data, env_hashes) or environment_match(path.encode(), env_hashes)):
45
+ rules.add('secret.env-match')
46
+ if data.startswith(b'version https://git-lfs.github.com/spec/v1'):
47
+ rules.add('scan.lfs-uninspected')
48
+ return rules, provenance
49
+
50
+
51
+ def working_files(repo):
52
+ for directory, children, files in os.walk(repo, followlinks=False):
53
+ children[:] = [name for name in children if name not in DEPENDENCY_DIRS]
54
+ for name in files:
55
+ if name == '.git':
56
+ continue
57
+ target = Path(directory) / name
58
+ path = target.relative_to(repo).as_posix()
59
+ try:
60
+ data = os.fsencode(os.readlink(target)) if target.is_symlink() else target.read_bytes()
61
+ except OSError:
62
+ raise InspectionError('scan.working-tree-unreadable') from None
63
+ yield path, data
64
+
65
+
66
+ def working_regular(repo, path):
67
+ # Anchor reads to opened directories and refuse links at every step.
68
+ # The file descriptor's bytes and mode prove the same regular preimage.
69
+ directory = None
70
+ try:
71
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
72
+ directory = os.open(repo, flags)
73
+ parts = path.split('/')
74
+ for part in parts[:-1]:
75
+ child = os.open(part, flags, dir_fd=directory)
76
+ os.close(directory)
77
+ directory = child
78
+ descriptor = os.open(parts[-1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
79
+ with os.fdopen(descriptor, 'rb') as file:
80
+ status = os.fstat(file.fileno())
81
+ if not stat.S_ISREG(status.st_mode):
82
+ return None
83
+ return file.read(), '100755' if status.st_mode & 0o111 else '100644'
84
+ except OSError:
85
+ return None
86
+ finally:
87
+ if directory is not None:
88
+ os.close(directory)
@@ -0,0 +1,87 @@
1
+ """Public CA metadata inside pinned WebPKI trust-bundle artifacts.
2
+
3
+ A committed trust-bundle.json, regenerated by the build that produces its
4
+ artifacts, pins each artifact's SHA-256 and lists the SHA-256 of every compiled
5
+ trust anchor's subject Name and SubjectPublicKeyInfo DER. Its webpki-roots
6
+ release must be the one the same directory's Cargo.lock locks. Only attribute
7
+ values of a subject Name parsed from such an artifact, whose anchor's subject
8
+ and public key both occur there as a listed pair, are certificate metadata.
9
+ """
10
+ from collections import namedtuple
11
+ from functools import lru_cache
12
+ import hashlib
13
+ import json
14
+ import re
15
+ from der import names, public_keys
16
+
17
+ MANIFEST = 'trust-bundle.json'
18
+ REGISTRY = 'registry+https://github.com/rust-lang/crates.io-index'
19
+ DIGEST = re.compile(r'[0-9a-f]{64}')
20
+ ARTIFACT = re.compile(r'[A-Za-z0-9_][A-Za-z0-9_.-]*')
21
+ Bundle = namedtuple('Bundle', 'digest anchors')
22
+
23
+
24
+ def locked_packages(lock):
25
+ packages, current = [], None
26
+ for line in lock.decode('utf-8').splitlines():
27
+ if line == '[[package]]':
28
+ current = {}
29
+ packages.append(current)
30
+ elif line.startswith('['):
31
+ current = None
32
+ elif current is not None and ' = "' in line and line.endswith('"'):
33
+ key, value = line.split(' = "', 1)
34
+ current[key] = value[:-1]
35
+ return packages
36
+
37
+
38
+ @lru_cache(maxsize=16)
39
+ def parse(manifest, lock):
40
+ try:
41
+ record = json.loads(manifest)
42
+ roots, artifacts, anchors = record['roots'], record['artifacts'], record['anchors']
43
+ if (set(record) != {'schemaVersion', 'roots', 'artifacts', 'anchors'} or record['schemaVersion'] != 1 or
44
+ set(roots) != {'crate', 'version', 'checksum'} or roots['crate'] != 'webpki-roots' or
45
+ not isinstance(roots['version'], str) or not DIGEST.fullmatch(roots['checksum']) or
46
+ not artifacts or not all(isinstance(name, str) and ARTIFACT.fullmatch(name) and isinstance(digest, str) and DIGEST.fullmatch(digest)
47
+ for name, digest in artifacts.items()) or
48
+ not anchors or not all(isinstance(a, dict) and set(a) == {'subject', 'spki'} and
49
+ all(isinstance(v, str) and DIGEST.fullmatch(v) for v in a.values()) for a in anchors)):
50
+ return None
51
+ locked = [p for p in locked_packages(lock) if p.get('name') == roots['crate']]
52
+ except (ValueError, TypeError, KeyError, AttributeError, UnicodeError):
53
+ return None
54
+ if len(locked) != 1 or locked[0] != dict(name=roots['crate'], version=roots['version'], source=REGISTRY, checksum=roots['checksum']):
55
+ return None
56
+ pairs = frozenset((a['subject'], a['spki']) for a in anchors)
57
+ return tuple((name, Bundle(digest, pairs)) for name, digest in artifacts.items())
58
+
59
+
60
+ def bundles(paths, read):
61
+ """{artifact path: Bundle} declared by valid manifests among a tree's paths."""
62
+ result = {}
63
+ for path in paths:
64
+ directory, _, name = path.rpartition('/')
65
+ prefix = directory + '/' if directory else ''
66
+ if name != MANIFEST or prefix + 'Cargo.lock' not in paths:
67
+ continue
68
+ for artifact, bundle in parse(read(path), read(prefix + 'Cargo.lock')) or ():
69
+ result[prefix + artifact] = bundle
70
+ return result
71
+
72
+
73
+ def anchor_values(data, bundle):
74
+ """Attribute value spans of verified trust-anchor subjects in exact pinned bytes."""
75
+ if hashlib.sha256(data).hexdigest() != bundle.digest:
76
+ return []
77
+ subjects = {subject for subject, _ in bundle.anchors}
78
+ keys = {spki for _, spki in bundle.anchors}
79
+ found = {}
80
+ for start, end, values in names(data):
81
+ digest = hashlib.sha256(data[start:end]).hexdigest()
82
+ if digest in subjects:
83
+ found.setdefault(digest, []).append(values)
84
+ present = {digest for start, end in public_keys(data)
85
+ if (digest := hashlib.sha256(data[start:end]).hexdigest()) in keys}
86
+ return [span for subject, spki in bundle.anchors if spki in present
87
+ for values in found.get(subject, ()) for span in values]
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdtempSync, readFileSync, openSync, closeSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ const args = process.argv.slice(2);
8
+ let seconds = 300;
9
+ for (let i = 0; i < args.length; i++) {
10
+ const raw = args[i] === '--timeout-seconds' ? args[i + 1] : args[i].startsWith('--timeout-seconds=') ? args[i].slice(18) : null;
11
+ if (raw !== null && Number(raw) > 0 && Number(raw) <= 7200) seconds = Number(raw);
12
+ }
13
+ const scratch = mkdtempSync(join(tmpdir(), 'publish-check-'));
14
+ const output = join(scratch, 'report.json');
15
+ const fd = openSync(output, 'wx', 0o600);
16
+ let child, reason, timer;
17
+ const stop = value => {
18
+ reason ??= value;
19
+ // The child remains unreaped until its close handler; descendants (native
20
+ // Git and Gitleaks) share the private process group and this total deadline.
21
+ if (child?.pid) {
22
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { /* already stopped */ }
23
+ }
24
+ };
25
+ const interrupt = () => stop('scan.interrupted');
26
+ process.on('SIGINT', interrupt);
27
+ process.on('SIGTERM', interrupt);
28
+ let report, code = 2;
29
+ try {
30
+ child = spawn('python3', ['-B', new URL('./publish-check/cli.py', import.meta.url).pathname, ...args], {
31
+ detached: true, stdio: ['ignore', fd, 'ignore'], env: { ...process.env, TMPDIR: scratch },
32
+ });
33
+ closeSync(fd);
34
+ timer = setTimeout(() => stop('scan.deadline-exceeded'), seconds * 1000);
35
+ const result = await new Promise(resolve => {
36
+ child.once('error', () => resolve(null));
37
+ child.once('close', value => resolve(value));
38
+ });
39
+ if (!reason && [0, 1, 2].includes(result)) {
40
+ report = JSON.parse(readFileSync(output, 'utf8'));
41
+ code = result;
42
+ } else reason ??= 'scan.worker-incomplete';
43
+ } catch { reason ??= 'scan.worker-incomplete'; }
44
+ finally {
45
+ clearTimeout(timer);
46
+ process.removeListener('SIGINT', interrupt);
47
+ process.removeListener('SIGTERM', interrupt);
48
+ rmSync(scratch, { recursive: true, force: true });
49
+ }
50
+ if (reason) report = {
51
+ schema: 'rer.publish-check/v3', status: 'INCOMPLETE', complete: false, source: null,
52
+ findings: [{ rule: reason, path: '.', commit: null, scope: 'newly-public' }],
53
+ preExisting: [], warnings: [], fixtures: [], reviewed: [], info: [],
54
+ history: { commits: 0, trees: 0, blobs: 0 },
55
+ guarantees: { historyScanned: false, visualRecognition: false, environmentChecked: false },
56
+ };
57
+ process.stdout.write(JSON.stringify(report) + '\n');
58
+ process.exitCode = code;