@@ -16,7 +16,11 @@ import {
1616 assessArtifactStaleness ,
1717 formatMtimeGap ,
1818} from '../utils/dev-restart.js' ;
19- import { isOrganizationOwnershipRequiredMessage , upgradeDevelopmentDatabase } from '../utils/dev-organization-ownership-upgrade.js' ;
19+ import {
20+ isOrganizationOwnershipRequiredMessage ,
21+ upgradeDevelopmentDatabase ,
22+ type DevelopmentUpgradeResult ,
23+ } from '../utils/dev-organization-ownership-upgrade.js' ;
2024import { childEnvWithResolvedArtifact } from '../utils/internal-artifact-channel.js' ;
2125import { makeDirectorySync } from '../utils/make-directory.js' ;
2226// THE artifact precedence, written once (#21501) — shared with `start`, and
@@ -247,6 +251,42 @@ export function devWatchActive(opts: { watch: boolean; artifact?: string; config
247251 return opts . watch && ! opts . artifact && opts . configExists ;
248252}
249253
254+ /**
255+ * What `os dev` prints when the organization-ownership boot guard refused its
256+ * `serve` child, and the upgrade it then runs (ADR-0131 D10, development
257+ * exception; `utils/dev-organization-ownership-upgrade.ts`). The coordinator's
258+ * `recover` calls this once per session; `upgrade` copies the development
259+ * database, runs the ceremony on it and answers its verdict.
260+ *
261+ * ⛔ The first line names no origin for the database. Every refusal the guard
262+ * throws reaches this path — a marker absent, unverified, unreadable or from an
263+ * older shape generation, a shape read that failed, a birth write that failed —
264+ * and only the absent marker means "17.x or an 18.0 pre-release created it". The
265+ * guard's own refusal, printed just above this line by the failed boot, already
266+ * names what it found, so this line points at it rather than carrying a second
267+ * copy of that wording, which would be wrong for the other five.
268+ *
269+ * The copy's path is printed by the verdict lines below, from the upgrade's own
270+ * result.
271+ *
272+ * @returns whether the upgrade finished and the server may boot again.
273+ */
274+ export async function upgradeRefusedDevelopmentDatabase ( io : {
275+ /** Writes one line for the developer. */
276+ print : ( line : string ) => void ;
277+ /** Copies the development database, then plans and applies the ceremony on it. */
278+ upgrade : ( ) => Promise < DevelopmentUpgradeResult > ;
279+ } ) : Promise < boolean > {
280+ io . print ( chalk . yellow (
281+ ' ⟳ The organization-ownership boot guard refused this development database (the refusal above), so '
282+ + 'os dev upgrades it in place, keeping a copy first…' ,
283+ ) ) ;
284+ const result = await io . upgrade ( ) ;
285+ for ( const warning of result . warnings ) io . print ( chalk . yellow ( ` ⚠ ${ warning } ` ) ) ;
286+ for ( const line of result . lines ) io . print ( result . ok ? chalk . green ( ` ✓ ${ line } ` ) : chalk . red ( ` ✗ ${ line } ` ) ) ;
287+ return result . ok ;
288+ }
289+
250290export default class Dev extends Command {
251291 static override description =
252292 'Start development mode — watch sources, rebuild the artifact, and restart the server on change' ;
@@ -725,11 +765,12 @@ export default class Dev extends Command {
725765 // load is the one path that applies the whole artifact, so the
726766 // coordinator restarts the serve child after each successful rebuild
727767 // (nodemon-style, default-on; opt out with --no-restart).
728- // [ADR-0131 D10, development exception] A serve child refused for the
729- // v17 organization-ownership shape says so before it exits; the
768+ // [ADR-0131 D10, development exception] A serve child the
769+ // organization-ownership boot guard refused says so before it exits; the
730770 // coordinator then asks `recover` below, which copies the development
731771 // database, runs the ceremony on it and boots the server again — once
732- // per session (`utils/dev-organization-ownership-upgrade.ts`).
772+ // per session (`utils/dev-organization-ownership-upgrade.ts`,
773+ // `upgradeRefusedDevelopmentDatabase` above for what it prints).
733774 let ownershipUpgrade : 'none' | 'pending' | 'attempted' = 'none' ;
734775 let upgradedBoot = false ;
735776 const spawnServeChild = ( info : { restartIndex : number } ) => {
@@ -818,28 +859,27 @@ export default class Dev extends Command {
818859 recover : async ( ) => {
819860 if ( ownershipUpgrade !== 'pending' ) return false ;
820861 ownershipUpgrade = 'attempted' ;
821- console . log ( chalk . yellow (
822- ' ⟳ This development database is still in the v17 organization-ownership shape — upgrading it in place '
823- + '(a copy is kept first)…' ,
824- ) ) ;
825- const { resolveStandaloneDatabase } = await import ( '@objectstack/runtime' ) ;
826- let sqliteFile : string | null = null ;
827- try {
828- sqliteFile = resolveStandaloneDatabase ( { projectRoot : process . cwd ( ) , databaseUrl : effectiveDb } ) . sqliteFile ;
829- } catch {
830- sqliteFile = null ;
831- }
832- const result = upgradeDevelopmentDatabase ( {
833- binPath,
834- databaseUrl : effectiveDb ,
835- sqliteFile,
836- env : localEnv ,
837- cwd : process . cwd ( ) ,
862+ const ok = await upgradeRefusedDevelopmentDatabase ( {
863+ print : ( line ) => console . log ( line ) ,
864+ upgrade : async ( ) => {
865+ const { resolveStandaloneDatabase } = await import ( '@objectstack/runtime' ) ;
866+ let sqliteFile : string | null = null ;
867+ try {
868+ sqliteFile = resolveStandaloneDatabase ( { projectRoot : process . cwd ( ) , databaseUrl : effectiveDb } ) . sqliteFile ;
869+ } catch {
870+ sqliteFile = null ;
871+ }
872+ return upgradeDevelopmentDatabase ( {
873+ binPath,
874+ databaseUrl : effectiveDb ,
875+ sqliteFile,
876+ env : localEnv ,
877+ cwd : process . cwd ( ) ,
878+ } ) ;
879+ } ,
838880 } ) ;
839- for ( const warning of result . warnings ) console . log ( chalk . yellow ( ` ⚠ ${ warning } ` ) ) ;
840- for ( const line of result . lines ) console . log ( result . ok ? chalk . green ( ` ✓ ${ line } ` ) : chalk . red ( ` ✗ ${ line } ` ) ) ;
841- if ( result . ok ) upgradedBoot = true ;
842- return result . ok ;
881+ if ( ok ) upgradedBoot = true ;
882+ return ok ;
843883 } ,
844884 log : ( line ) => {
845885 const trimmed = line . trimStart ( ) ;
0 commit comments