You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Track the org-wide repository refactor to completion. Two phases, in this order:
Deploy-repo / library-repo split — every repo is strictly one or the other, never both.
Interface absorption — once a core repo is a pure library, fold its *.interface repo back into it.
Phase 1 for a given core repo is a hard prerequisite of phase 2 for that repo. See the phase 2 section for why the order cannot be reversed.
Phase 1: the split
Library repo — autopublishes to Soldeer on merge via rainix-autopublish (next-version lifecycle). No concrete deployables, no deploy pins, no frozen snapshots.
Deploy repo — releases only on a manual sol-v* tag via rainix-tag-release. Carries the concrete contracts, the deployed address/codehash pins, and the frozen per-tag src/generated/<tag>/ snapshots.
rain.factory was the practise run (rainlanguage/rain.factory#46). This issue is the org-wide rollout it was a rehearsal for.
Why: the two lifecycles are mutually exclusive
A library bumps [package].version on every content change. A deploy record's tag may only move when a real deploy happens. Hold both in one repo and the next-version bump on every merge desyncs [package].version from the frozen deploy tag — permanently-red drift.
This is no longer theoretical. Measured across the org on 2026-08-13:
Release lanes currently wedged ([package].version equals the published revision, so rainix-autopublish's next-version invariant rejects every run):
repo
slot = published
deploy artifacts / concrete
raindex
0.1.13
78 / 8
rainlang
0.1.8
9 / 7
rain.math.float
0.1.7
3 / 1
rain.flare
0.1.2
2 / 1
rain.pyth
0.1.0
2 / 1
rain.vats
0.2.0
0 / 15
Every repo carrying neither a deploy artifact nor a concrete contract publishes cleanly — five consecutive successes each: rain.solmem, rain.string, rain.lib.hash, rain.lib.memkv, rain.lib.typecast, rain.math.fixedpoint, rain.math.binary, rain.math.saturating, rain.sol.binmaskflag, rain.intorastring, rain.datacontract, rainlang.interface, raindex.interface.
Two honest qualifiers:
Carrying both halves does not guarantee a wedge. rain.extrospection has 1 deploy artifact and 1 concrete contract and publishes fine. The conflict creates the possibility; a half-completed publish realises it.
rain.vats is wedged with zero deploy artifacts (15 concrete contracts, no Deploy.sol, no src/generated/). Different shape — needs its own diagnosis, not a split.
Not the same problem, don't conflate
startup_failure class — diagnosed, unrelated to the split. rain.flare, rain.tofu.erc20-decimals and rain.verify.interface never create a single job. rainix-autopublish's release job declares permissions: {contents: write, id-token: write}, a called workflow may only narrow the token it is handed, and these repos have default_workflow_permissions: read with no caller permissions: block — so GitHub rejects the run before creating any job:
The nested job 'release' is requesting 'contents: write, id-token: write',
but is only allowed 'contents: read, id-token: none'.
Systemic fragility worth fixing generally:none of the 30 consumers declares a caller permissions: block, so the 27 that work do so only by accident of a permissive repo setting. Flipping any repo to read breaks its release lane instantly, with a failure mode that produces no logs, no jobs and no annotations.
Silent missed releases — rain.metadata has published 0.1.0 against a 0.1.3 slot: three releases that did not happen.
rainix#273 — decoupling snapshot-cutting and deploying from publish. Mechanism-level, and complementary to this; the split does not remove the need for it.
State
Done:
rainix#280 — rainix-tag-release (tag-triggered deploy-repo release) merged, workflow live
rain.factory.deploy created with the deploy half
rain.factory#47 — library slimmed to interfaces-only, merged 2026-08-13 (8a5c603a)
rain.factory, remaining:
rain.factory.deploy#5 (rolling-candidate snapshot model) — green, ready. Must land before the first tag: it changes what the tag does
rain.factory.deploy#4 — close as superseded by debug deploy #5 (conflicts textually and semantically; debug deploy #5 makes DEPLOY_TAG the constant "candidate" while 2024 01 09 tauri #4 asserts DEPLOY_TAG == [package].version)
rain.factory#51 — re-scope: ICloneableFactoryV4 stays in rain.factory; the open-salt implementation, its snapshot and tests belong in rain.factory.deploy
HUMAN — fix the fork-RPC secrets, dispatch the on-chain deploy, then cut a fresh sol-v* tag. rain-factory-deploy has never published; sol-v0.1.6 is spent (points five commits behind main, and its run died on the <NETWORK>_RPC_URL naming mismatch since fixed upstream). Version 0.1.6 is still free on the registry
Consumer migration — scope is S01-Issuer/st0x.deploy alone, two files. rainix is not a consumer: the LibCloneFactoryDeploy hits in rainix-static/src/frozen_snapshots.rs are unit-test fixture strings
Next candidates, factory-shaped (one concrete contract, 1–3 deploy artifacts) and independent of each other:
With deployment out and everything on Soldeer rather than submodules, the separate *.interface repos stop earning their keep. Fold each back into its core repo.
rainlang.interface → rainlang — Absorb rainlang-interface back into rainlang (rainix#296) rainlang.interface#120. (publishes rainlang-interface). 7 of 9 consumers — rain.erc4626.words, rain.flare, raindex, rain.dia, rain.pyth, rain.merkle, rain.verify — already pin rainlang as well, so for them the split buys nothing but two pins and two sets of versioned import prefixes to keep in step. Interface-only consumers: raindex.governance, raindex.interface. rainlang's own dependency on its interface disappears entirely.
Absorption shortens the republish cascade. A leaf-library bump today needs rain-interpreter-interface republished, thenrainlang, before any leaf repo can move — this is exactly what blocked the rain-math-binary 0.1.1 → 0.1.3 campaign on 2026-08-13, parking six repos behind two lockstep republishes. Merged, that is one republish, and every future leaf bump gets one link shorter.
Compile cost of a fatter package is near zero: Foundry compiles only what src/test/script transitively import, so unreached source is never parsed. Verified in that same campaign — rain.verify bumped green while siblings failed, precisely because it never reaches the offending file.
Cost, stated honestly
Under the versioned-import convention, absorption couples interface-only consumers to the implementation's release cadence: they re-pin and rewrite import prefixes on every implementation release, not just on interface changes. Interfaces are stable and implementations churn, so this is a real cost. It lands on four repos — raindex.governance, raindex.interface, rain.vats, rain.vats.flare — rather than org-wide.
Ordering is a hard dependency, not a preference
The interface split is currently protecting the interfaces from their cores' wedged lanes: rainlang.interface publishes cleanly precisely because it carries no deploy half, while rainlang is wedged at 0.1.8 and raindex at 0.1.13. Folding an interface into a wedged core propagates the failure instead of escaping it. For each repo: complete phase 1, confirm the lane publishes, then absorb.
Ask
Track the org-wide repository refactor to completion. Two phases, in this order:
*.interfacerepo back into it.Phase 1 for a given core repo is a hard prerequisite of phase 2 for that repo. See the phase 2 section for why the order cannot be reversed.
Phase 1: the split
rainix-autopublish(next-version lifecycle). No concrete deployables, no deploy pins, no frozen snapshots.sol-v*tag viarainix-tag-release. Carries the concrete contracts, the deployed address/codehash pins, and the frozen per-tagsrc/generated/<tag>/snapshots.rain.factory was the practise run (rainlanguage/rain.factory#46). This issue is the org-wide rollout it was a rehearsal for.
Why: the two lifecycles are mutually exclusive
A library bumps
[package].versionon every content change. A deploy record's tag may only move when a real deploy happens. Hold both in one repo and the next-version bump on every merge desyncs[package].versionfrom the frozen deploy tag — permanently-red drift.This is no longer theoretical. Measured across the org on 2026-08-13:
Release lanes currently wedged (
[package].versionequals the published revision, sorainix-autopublish's next-version invariant rejects every run):Every repo carrying neither a deploy artifact nor a concrete contract publishes cleanly — five consecutive successes each: rain.solmem, rain.string, rain.lib.hash, rain.lib.memkv, rain.lib.typecast, rain.math.fixedpoint, rain.math.binary, rain.math.saturating, rain.sol.binmaskflag, rain.intorastring, rain.datacontract, rainlang.interface, raindex.interface.
Two honest qualifiers:
Deploy.sol, nosrc/generated/). Different shape — needs its own diagnosis, not a split.Not the same problem, don't conflate
startup_failureclass — diagnosed, unrelated to the split. rain.flare, rain.tofu.erc20-decimals and rain.verify.interface never create a single job.rainix-autopublish'sreleasejob declarespermissions: {contents: write, id-token: write}, a called workflow may only narrow the token it is handed, and these repos havedefault_workflow_permissions: readwith no callerpermissions:block — so GitHub rejects the run before creating any job:Exception-free correlation across all 30 org repos calling
rainix-autopublish: 3/3 withreadfail at startup, 27/27 withwritestart normally. Fix is an explicitpermissions:block on the calling job — fix(ci): grant Package Release the token permissions its callee needs rain.flare#206 and fix(ci): grant Package Release the token permissions its callee needs rain.tofu.erc20-decimals#28. rain.verify.interface still needs the same one-line fix — Package Release never starts: caller declares no permissions block rain.verify.interface#14. Systemic follow-up: rainix#297.Systemic fragility worth fixing generally: none of the 30 consumers declares a caller
permissions:block, so the 27 that work do so only by accident of a permissive repo setting. Flipping any repo toreadbreaks its release lane instantly, with a failure mode that produces no logs, no jobs and no annotations.Silent missed releases — rain.metadata has published 0.1.0 against a 0.1.3 slot: three releases that did not happen.
rainix#273 — decoupling snapshot-cutting and deploying from publish. Mechanism-level, and complementary to this; the split does not remove the need for it.
State
Done:
rainix-tag-release(tag-triggered deploy-repo release) merged, workflow live8a5c603a)rain.factory, remaining:
DEPLOY_TAGthe constant"candidate"while 2024 01 09 tauri #4 assertsDEPLOY_TAG == [package].version)ICloneableFactoryV4stays in rain.factory; the open-salt implementation, its snapshot and tests belong in rain.factory.deploysol-v*tag.rain-factory-deployhas never published;sol-v0.1.6is spent (points five commits behind main, and its run died on the<NETWORK>_RPC_URLnaming mismatch since fixed upstream). Version 0.1.6 is still free on the registryLibCloneFactoryDeployhits inrainix-static/src/frozen_snapshots.rsare unit-test fixture stringsNext candidates, factory-shaped (one concrete contract, 1–3 deploy artifacts) and independent of each other:
Then the large ones, which should follow the small proofs rather than lead them:
Separately:
Phase 2: interface absorption
With deployment out and everything on Soldeer rather than submodules, the separate
*.interfacerepos stop earning their keep. Fold each back into its core repo.rainlang-interface). 7 of 9 consumers — rain.erc4626.words, rain.flare, raindex, rain.dia, rain.pyth, rain.merkle, rain.verify — already pinrainlangas well, so for them the split buys nothing but two pins and two sets of versioned import prefixes to keep in step. Interface-only consumers: raindex.governance, raindex.interface. rainlang's own dependency on its interface disappears entirely.raindex-interface). One external interface-only consumer: raindex.governance. raindex's own dependency on it disappears.rain-verify-interface). Interface-only consumers: rain.vats, rain.vats.flare. rain.verify's own dependency on it disappears. Note this repo also carries thestartup_failuremisconfiguration above.Why
Absorption shortens the republish cascade. A leaf-library bump today needs
rain-interpreter-interfacerepublished, thenrainlang, before any leaf repo can move — this is exactly what blocked therain-math-binary0.1.1 → 0.1.3 campaign on 2026-08-13, parking six repos behind two lockstep republishes. Merged, that is one republish, and every future leaf bump gets one link shorter.Compile cost of a fatter package is near zero: Foundry compiles only what
src/test/scripttransitively import, so unreached source is never parsed. Verified in that same campaign — rain.verify bumped green while siblings failed, precisely because it never reaches the offending file.Cost, stated honestly
Under the versioned-import convention, absorption couples interface-only consumers to the implementation's release cadence: they re-pin and rewrite import prefixes on every implementation release, not just on interface changes. Interfaces are stable and implementations churn, so this is a real cost. It lands on four repos — raindex.governance, raindex.interface, rain.vats, rain.vats.flare — rather than org-wide.
Ordering is a hard dependency, not a preference
The interface split is currently protecting the interfaces from their cores' wedged lanes:
rainlang.interfacepublishes cleanly precisely because it carries no deploy half, whilerainlangis wedged at 0.1.8 andraindexat 0.1.13. Folding an interface into a wedged core propagates the failure instead of escaping it. For each repo: complete phase 1, confirm the lane publishes, then absorb.Related
rainix-tag-release