@@ -111,7 +111,7 @@ func newLoopContext(repoPath, delivery, baseRef string) (*loopContext, error) {
111111 return nil , err
112112 }
113113 }
114- store := newFileStore (repo .GitDir , instance , program . Identity () )
114+ store := newFileStore (repo .GitDir , instance )
115115 domain := & reviewDomain {repo : repo , store : store , policy : policy , baseRef : baseRef }
116116 return & loopContext {
117117 repo : repo ,
@@ -128,14 +128,27 @@ func newLoopContext(repoPath, delivery, baseRef string) (*loopContext, error) {
128128func (c * loopContext ) runtime () (kernel.Runtime , error ) {
129129 return kernel .NewRuntime (
130130 c .program , c .domain , c .operator , reviewCapabilities {},
131- c .store , directoryLocker {path : c .store . lockPath () }, c .clock ,
131+ c .store , directoryLocker {store : c .store }, c .clock ,
132132 )
133133}
134134
135135func (c * loopContext ) authority (actor string , capabilities ... kernel.Capability ) (kernel.Authority , error ) {
136136 return localAuthority (actor , c .clock .Now (), capabilities ... )
137137}
138138
139+ // ensureProvisioned provisions the command's control instance when it does
140+ // not exist yet. Only the mutating entrypoints call it; read-only commands
141+ // surface the typed not-found result instead of manufacturing state.
142+ func (c * loopContext ) ensureProvisioned (runtime kernel.Runtime ) error {
143+ if _ , err := c .store .Load (context .Background (), c .instance ); ! kernel .IsInstanceNotFound (err ) {
144+ return err
145+ }
146+ if _ , err := runtime .Provision (context .Background (), c .instance ); err != nil && ! kernel .IsInstanceExists (err ) {
147+ return err
148+ }
149+ return nil
150+ }
151+
139152func printJSON (value any ) error {
140153 encoder := json .NewEncoder (os .Stdout )
141154 encoder .SetIndent ("" , " " )
@@ -194,6 +207,9 @@ func commandResolve(arguments []string) error {
194207 if err != nil {
195208 return err
196209 }
210+ if err := loop .ensureProvisioned (runtime ); err != nil {
211+ return err
212+ }
197213 authority , err := loop .authority (* actor , capabilitySubmit )
198214 if err != nil {
199215 return err
@@ -276,6 +292,9 @@ func commandSubmit(arguments []string) error {
276292 if err != nil {
277293 return err
278294 }
295+ if err := loop .ensureProvisioned (runtime ); err != nil {
296+ return err
297+ }
279298 authority , err := loop .authority (* actor , capabilitySubmit )
280299 if err != nil {
281300 return err
@@ -300,10 +319,11 @@ func commandSubmit(arguments []string) error {
300319 if err != nil {
301320 return fmt .Errorf ("submission did not commit: %w" , err )
302321 }
303- state , err := loop .store .Load (context .Background (), loop .instance )
322+ record , err := loop .store .Load (context .Background (), loop .instance )
304323 if err != nil {
305324 return err
306325 }
326+ state := record .State
307327 observed , err := loop .domain .observeValue ()
308328 if err != nil {
309329 return err
@@ -346,35 +366,62 @@ func commandStatus(arguments []string) error {
346366 if err != nil {
347367 return err
348368 }
349- state , err := loop .store .Load (context .Background (), loop .instance )
350- if err != nil {
351- return err
352- }
353- observed , err := loop .domain .observeValue ()
369+ report , err := loop .statusReport ()
354370 if err != nil {
355371 return err
356372 }
357- stale := state .Program != loop .program .Identity ()
358- return printJSON (struct {
359- Instance string `json:"instance"`
360- Program kernel.ProgramIdentity `json:"program"`
361- State kernel.ControlState `json:"state"`
362- ProgramStale bool `json:"program_stale"`
363- Observation observationValue `json:"observation"`
364- Guidance string `json:"guidance,omitempty"`
365- }{
366- Instance : loop .instance ,
367- Program : loop .program .Identity (),
373+ return printJSON (report )
374+ }
375+
376+ type statusReport struct {
377+ Instance string `json:"instance"`
378+ Program kernel.ProgramIdentity `json:"program"`
379+ State kernel.ControlState `json:"state"`
380+ Provisioned bool `json:"provisioned"`
381+ ProgramStale bool `json:"program_stale"`
382+ Observation observationValue `json:"observation"`
383+ Guidance string `json:"guidance,omitempty"`
384+ }
385+
386+ // statusReport observes the committed control state read-only. It never
387+ // provisions: for a missing instance it reports the deterministic initial
388+ // state the first mutating command will create, marked unprovisioned,
389+ // without persisting anything.
390+ func (c * loopContext ) statusReport () (statusReport , error ) {
391+ record , err := c .store .Load (context .Background (), c .instance )
392+ provisioned := true
393+ if kernel .IsInstanceNotFound (err ) {
394+ provisioned = false
395+ record = kernel.InstanceRecord {State : kernel.ControlState {
396+ InstanceID : c .instance , Program : c .program .Identity (),
397+ Mode : c .program .InitialMode , Revision : 1 ,
398+ }}
399+ } else if err != nil {
400+ return statusReport {}, err
401+ }
402+ state := record .State
403+ observed , err := c .domain .observeValue ()
404+ if err != nil {
405+ return statusReport {}, err
406+ }
407+ stale := state .Program != c .program .Identity ()
408+ return statusReport {
409+ Instance : c .instance ,
410+ Program : c .program .Identity (),
368411 State : state ,
412+ Provisioned : provisioned ,
369413 ProgramStale : stale ,
370414 Observation : observed ,
371415 Guidance : func () string {
416+ if ! provisioned {
417+ return "this control instance is not provisioned yet; the first mutating command (`boatstack-reviewer resolve`) provisions it"
418+ }
372419 if stale {
373420 return "the admitted policy or law changed since this state was committed; `boatstack-reviewer reset --confirm` archives it"
374421 }
375422 return submissionGuidance (state .Mode )
376423 }(),
377- })
424+ }, nil
378425}
379426
380427// commandShow prints a recorded review itself — the exact archived findings
@@ -392,10 +439,14 @@ func commandShow(arguments []string) error {
392439 if err != nil {
393440 return err
394441 }
395- state , err := loop .store .Load (context .Background (), loop .instance )
442+ record , err := loop .store .Load (context .Background (), loop .instance )
443+ if kernel .IsInstanceNotFound (err ) {
444+ return fmt .Errorf ("%w; run `boatstack-reviewer resolve` to provision it" , err )
445+ }
396446 if err != nil {
397447 return err
398448 }
449+ state := record .State
399450 journal , err := loop .store .loadJournal ()
400451 if err != nil {
401452 return err
@@ -491,7 +542,7 @@ func commandSeal(arguments []string) error {
491542 if ! report .Verified {
492543 return fmt .Errorf ("seal refused: the full receipt does not verify: %s" , strings .Join (report .Failures , "; " ))
493544 }
494- archive := filepath .Join (loop .store .dir , "sealed-receipt.json" )
545+ archive := filepath .Join (loop .store .dir () , "sealed-receipt.json" )
495546 if err := writeSealedReceipt (archive , receipt ); err != nil {
496547 return err
497548 }
@@ -576,6 +627,9 @@ func commandRequested(arguments []string, name, transition string, capability ke
576627 if err != nil {
577628 return err
578629 }
630+ if err := loop .ensureProvisioned (runtime ); err != nil {
631+ return err
632+ }
579633 authority , err := loop .authority (* actor , capability )
580634 if err != nil {
581635 return err
@@ -615,9 +669,9 @@ func commandReset(arguments []string) error {
615669 return err
616670 }
617671 if ! * confirm {
618- return fmt .Errorf ("reset archives %s; pass --confirm to proceed" , loop .store .dir )
672+ return fmt .Errorf ("reset archives %s; pass --confirm to proceed" , loop .store .dir () )
619673 }
620- if _ , err := os .Stat (loop .store .dir ); os .IsNotExist (err ) {
674+ if _ , err := os .Stat (loop .store .dir () ); os .IsNotExist (err ) {
621675 return fmt .Errorf ("instance %s has no local review state" , loop .instance )
622676 }
623677 archived , err := loop .store .archive (time .Now ().UTC ().Format ("20060102T150405Z" ))
0 commit comments