on master help sign in
Recover direct program initializer context

Derive a missing initializer's expected gate from the resolved program components with fresh IDs, source-backed positional fitting, and stable replay. Infer the actual tuple-bodied initializer and main ordinarily before applying all finalization, kind, and graph checks. Share ordinary definition finalization between the resolver and retry. Cover exact exported and imported types, unfaced updates, freshness, and fail-closed boundaries under eval and on the ship.

commit26bf7a7c82af19331a6b1b33b7cf03dc516bcad6 authoriko <mail@iko.soy> dateSun 1 Nov 2026, 02:58 (2 days ago) parentb4703e7
20 files changed+502−22
README.md+1−1 docs/compiler.md+2 docs/development.md+2 docs/status.md+5−3 jibb-dev/lib/jibb.hoon+182−17 tests/lib/jibb-program-init.hoon+135 tests/type-program-main/initializer-consumer.txt+2 tests/type-program-main/initializer-earlier.txt+10 tests/type-program-main/initializer-error.txt+10 tests/type-program-main/initializer-generic.txt+10 tests/type-program-main/initializer-message.txt+13 tests/type-program-main/initializer-missing.txt+9 tests/type-program-main/initializer-model.txt+11 tests/type-program-main/initializer-no-main.txt+10 tests/type-program-main/initializer-subs.txt+10 tests/type-program-main/initializer-tag.txt+13 tests/type-program-main/initializer-unfaced.txt+10 tests/type-program-main/initializer.txt+12 tools/testdata/ship-selftest+6−1 tools/type-program-init-test+49
README.md+1−1
@@ -44,7 +44,7 @@ tools/hoon --raw --cord src=tests/golden/foo/source.jibb 'tree:(golden /a/b/jibb
4444
4545`tools/testdata/selftest` tests both tools against a stub library, and `tools/doc-blocks` against Markdown it writes. `tools/importer-test` compares the partial Hoon importer's fixture projections against their golden file. `tools/type-import-test`, `tools/type-import-overlap-test`, `tools/type-import-structural-test`, `tools/type-union-source-test`, `tools/type-union-alias-overlap-test`, `tools/type-full-overlap-test`, `tools/type-generic-recursion-test`, `tools/type-generic-construction-test`, `tools/type-generic-pattern-test`, `tools/type-deferred-test`, `tools/type-deferred-prepared-test`, `tools/type-deferred-resolver-test`, `tools/type-deferred-context-test`, `tools/type-empty-test`, `tools/type-cell-branch-test`, `tools/type-tlon-test`, `tools/type-mark-test`, `tools/type-poke-test` and `tools/type-api-paths-test` check importer/checker, prelude and partial API path analysis boundaries under eval; the ship suite runs the same fixtures.
4646
47−The focused `tools/type-program-main-test` checks the narrow closed, subscription-free `main` slice and its shared fake-ship fixture; it does not run an island or claim runtime watchability.
47+The focused `tools/type-program-main-test` checks the narrow closed, subscription-free `main` slice and its shared fake-ship fixture. `tools/type-program-init-test` checks transactional initializer recovery for a synthetic direct program, including exact exported and imported types and rejection boundaries. Neither runs an island or claims runtime watchability.
4848
4949### The doc's examples
5050
docs/compiler.md+2
@@ -130,6 +130,8 @@ A two-item tuple with an explicit term, null or loobean head can receive payload
130130
131131A full ordinary module may export a narrow checked program `main` only as a direct, unqualified `(program init update view subs)` call with four simple wing arguments, modulo transparent grouping. The canonical prepared prelude must supply both the `program` value and mold. The ordinary source-backed program fit and all normal mold, value and request-graph checks complete first. The inferred `main` must be closed and invariantly match an instantiated `program` mold from that prelude. The initializer must have a closed gate scheme; its data and model contain no open variables and share the existing 256-node gate-free budget, with `%yes` required for both. The named subscription gate must syntactically return null through transparent casts, groups and definition-free bodies. Arbitrary or aliased mains, API/store kinds, gates or unknowns in data/model, and nonempty subscriptions fail closed. This retains source and graph evidence and permits ordinary downstream imports; it neither executes the program nor proves runtime watchability.
132132
133+When only that program's initializer and `main` are unresolved, a bounded transactional retry can derive the initializer's expected gate from the resolved update, view and subscription components. It instantiates all schemes above the greatest variable ID in the module and prelude, reuses the source-backed positional fit and stable replay, and leaves only the initializer's data variable open. The named initializer must be a direct gate whose transparent body is a tuple ending in null. Its own sample fixes data; ordinary inference, product and whole-gate fits, pending-tag and finalization checks, and a fresh ordinary `main` inference must all succeed before the ordinary kind and graph checks publish anything. Aliases, non-main libraries, simultaneous unresolved subscriptions, nonempty subscriptions and unrelated pending tags decline.
134+
133135Each case's canonical API identity and source-derived schema travel across imports in a nonrecursive graph certificate containing the exact source bundle, prelude, reserved scope and trusted reader results. Cached interfaces must match those inputs and cannot hide an active ancestor; unknown legacy entries are rebuilt from their unique supplied source. Request-bearing closures are limited to 256 paths and requests. Closed gate samples can also fit when an unnamed input slot corresponds to a named one, provided products are identical and nested value faces and ambiguous cell tails remain intact. This is type inference only: request lowering, runtime schema validation and value watchability proofs are not implemented.
134136
135137`+golden-types-with-prelude` accepts case-local Jibb module sources alongside `/sur` and mark results and the vendored prelude source. It resolves reachable ordinary library modules in dependency order, caching completed interfaces per invocation and exporting supported closed value schemes, direct generic unions, and simple or nonrecursive generic mold aliases whose bodies the fragment supports. It detects missing sources and cycles and rejects a dependency if its interface cannot be inferred by the fragment. Qualified wings can also read nested fields of an imported value, instantiating that value’s scheme independently at each use; missing, ambiguous and non-record fields remain unsupported.
docs/development.md+2
@@ -92,6 +92,7 @@ git clone https://github.com/urbit/tools /root/src/tools
9292| `tools/type-todo-api-test` | Exercises the actual todo API through the real Hoon readers and partial checker |
9393| `tools/type-direct-gate-test` | Checks a synthetic source-backed program report and direct gate provenance under eval and on the ship |
9494| `tools/type-program-main-test` | Checks the narrow closed subscription-free program-main interface, downstream import and rejection guards under eval and on the ship |
95+| `tools/type-program-init-test` | Checks transactional direct-program initializer recovery, exact exported/imported types and fail-closed boundaries under eval and on the ship |
9596| `tools/type-closed-gate-guards-test` | Checks closed gate sample labels and face uncertainty under eval and on the ship |
9697| `tools/type-constructor-fit-guards-test` | Checks constructor-case uncertainty and known mismatches under eval and on the ship |
9798| `tools/type-nested-union-guards-test` | Checks nested tagged-union case selection and uncertainty under eval and on the ship |
@@ -141,6 +142,7 @@ tools/type-todo-partial-test # unchanged todo partial report and reader fa
141142tools/type-todo-api-test # real todo API partial inference and reader guards pass
142143tools/type-direct-gate-test # direct gate source and provenance guards pass
143144tools/type-program-main-test # closed program-main interface and rejection guards pass
145+tools/type-program-init-test # transactional direct-program initializer context guards pass
144146tools/type-generic-recursion-test # source-backed recursive identities and provenance
145147tools/type-generic-construction-test # source-backed recursive construction and bounds
146148tools/type-generic-pattern-test # source-backed recursive patterns and bounds
docs/status.md+5−3
@@ -43,7 +43,7 @@ A separate source-backed analysis can prove one named mold gate-free, report a d
4343
4444The partial checker can also match a directly named, checked root gate against a generic gate sample in a direct, unshadowed source-backed prelude call. It uses the written two-slot sample layouts to preserve nested record faces, then requires the complete call samples to be closed and tag-free and repeats the fit under the final substitution. The proof falls back to existing inference for unavailable or ambiguous source layouts, local or dynamic gates, aliases, recursive placeholders and open generic caller types. It preserves written-capital checks and projection evidence; it does not add `program`, check islands or prove request watchability. Prepared prelude source and interface remain a trusted pair.
4545
46−A narrow full-module path now checks direct four-wing `(program init update view subs)` definitions named `main`. It requires the canonical prepared `program` value and mold, reuses ordinary source-backed program fitting, and publishes only after all ordinary module and request-graph checks. The inferred `main` must be closed and invariantly match the instantiated `program` mold. Initializer data and model must be closed and gate-free with `%yes` verdicts inside a shared 256-node budget, and `subs` must syntactically return null through transparent forms. Aliases, arbitrary mains, API/store kinds, gate-bearing or unknown data/model, mismatched programs and nonempty subscriptions remain rejected. Checked interfaces retain source evidence and support ordinary downstream imports; no program execution or runtime watchability is claimed.
46+A narrow full-module path now checks direct four-wing `(program init update view subs)` definitions named `main`. It requires the canonical prepared `program` value and mold, reuses ordinary source-backed program fitting, and publishes only after all ordinary module and request-graph checks. The inferred `main` must be closed and invariantly match the instantiated `program` mold. Initializer data and model must be closed and gate-free with `%yes` verdicts inside a shared 256-node budget, and `subs` must syntactically return null through transparent forms. Aliases, arbitrary mains, API/store kinds, gate-bearing or unknown data/model, mismatched programs and nonempty subscriptions remain rejected. Checked interfaces retain source evidence and support ordinary downstream imports; no program execution or runtime watchability is claimed. A transactional extension can recover a direct initializer whose transparent tuple body ends in null when update, view and subs are already resolved. It derives the expected gate through the ordinary positional program fit with fresh IDs and stable replay, then re-infers the actual initializer and `main` under all ordinary finalization and kind checks. Non-main libraries, aliases, nonempty or simultaneously unresolved subscriptions, and unrelated pending tags remain rejected.
4747
4848The API path analyzer can retain its source, complete prepared prelude, reader results and resolved dependencies as evidence, and classify a unique declaration as watchable, not watchable or unknown. It does not yet prove the provenance of request values or check island subscriptions. Prepared artifacts and reader results remain trusted host inputs.
4949
@@ -96,7 +96,7 @@ What the plan asks for, and where it is:
9696Besides the plan's list:
9797
9898- 659 golden cases: 439 that parse and 220 that fail, each failure at a stated line and column. One failing case has an unclear expectation (corpus question 3). The newer type-output cases exercise only the partial inference fragment. Each case's `about.txt` quotes the doc's words it tests.
99−- The 659-case corpus passed under `urbit eval` in 90.4 s while other checks were running. The fake ship passed all 707 tests, including the source-overlap, structural-overlap, generic-union source and alias-overlap, full-module overlap, deferred-context, generic-recursion, generic-construction, generic-pattern, deferred-inference, empty-mold and cell-branch fixtures, importer, importer/checker and mark/checker bridges, module scope, unification, hole, qath, ask, default-wrapper importer, projection, arithmetic, generic-alias, positional-face, API-evidence, request, closed-gate, todo API, constructor, sub, nested-union, gate-free and direct-gate guards and the unchanged todo report. Desk sync took 86.2 s while other checks were running. The importer golden compares 221 projections; `tools/clay-fixtures` builds the example Hoon fixtures through Clay, checks a recursive Ford import's origin, and passes Clay-built types through the Clay-built importer and partial checker.
99+- The 659-case corpus passed under `urbit eval` in 92.8 s while other checks were running. The fake ship passed all 708 tests, including the source-overlap, structural-overlap, generic-union source and alias-overlap, full-module overlap, deferred-context, generic-recursion, generic-construction, generic-pattern, deferred-inference, empty-mold and cell-branch fixtures, importer, importer/checker and mark/checker bridges, module scope, unification, hole, qath, ask, default-wrapper importer, projection, arithmetic, generic-alias, positional-face, API-evidence, request, closed-gate, todo API, constructor, sub, nested-union, gate-free and direct-gate guards and the unchanged todo report. Desk sync took 93.1 s while other checks were running. The importer golden compares 221 projections; `tools/clay-fixtures` builds the example Hoon fixtures through Clay, checks a recursive Ford import's origin, and passes Clay-built types through the Clay-built importer and partial checker.
100100- `tools/testdata/selftest` (22 checks), `tools/testdata/ship-selftest` and `tools/testdata/clay-fixtures-selftest` test the tools themselves; the last checks temporary Clay fixture cleanup and signal handling without a ship.
101101- Two independent audits before 7c739b7, one of which compared the library with a reference parser written in Python from the doc over about 19,000 generated programs. Before ab797a9, a second Python parser, written from the decisions, agreed with the library on 8,000 generated programs and 900 hand-written probes.
102102- Speed: starting `urbit eval` and building the library takes about 8 s. After that, a 2,000-line module of 400 definitions parses in about a second. Loops over definitions, arms, lines and items grow no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (library section 3).
@@ -211,7 +211,8 @@ Besides the plan's list:
211211| b5cdc80 | 2026-10-31 | Preserve full-module overlap source evidence |
212212| ab6fdc1 | 2026-10-31 | Add source-backed text conversion wrappers |
213213| aa277c9 | 2026-10-31 | Recover prepared tuple initializers in partial reports |
214−| This change | 2026-10-31 | Check narrow closed subscription-free program mains |
214+| b4703e7 | 2026-11-01 | Check narrow closed subscription-free program mains |
215+| This change | 2026-11-01 | Recover narrow direct-program initializer contexts |
215216
216217## Checking it
217218
@@ -250,6 +251,7 @@ tools/type-ask-guards-test # ask source and bootstrap guards
250251tools/type-proj-guards-test # projection evidence and scope guards
251252tools/type-module-scope-test # module and prelude scope fixture
252253tools/type-program-main-test # closed program-main interface and rejection guards
254+tools/type-program-init-test # transactional direct-program initializer context guards
253255tools/hoon check-spans-test # prints <<>> when the span checker works
254256tools/testdata/selftest # the tools themselves
255257tools/doc-blocks docs/spec # the doc's examples are golden cases
jibb-dev/lib/jibb.hoon+182−17
@@ -10237,6 +10237,29 @@
1023710237 (resolve-groups-core ds molds globals externals surs mark-externals marks jimports reserved-molds at prelude ~)
1023810238::
1023910239
10240++$ iadded-definition
10241+ [env=ienv checked=(map term def) artifacts=(list iwidth-definition)]
10242+++ add-supported-result
10243+ |= [d=def result=iresult initial=istate env=ienv checked=(map term def) artifacts=(list iwidth-definition)]
10244+ ^- (unit iadded-definition)
10245+ ?> ?=(%value -.d)
10246+ ?: ?=(%| -.result) ~
10247+ =. state.p.result (promote-restricted-numbers state.p.result env)
10248+ =. state.p.result (default-hole-numbers holes.state.p.result holes.initial state.p.result env !(itype-has-gate (itype-normalize typ.p.result sub.state.p.result)))
10249+ ?^ (unresolved-hole holes.state.p.result holes.initial state.p.result) ~
10250+ ?: (constrained-capitals caps.state.p.result sub.state.p.result) ~
10251+ =/ typ=itype (default-decimals (itype-normalize typ.p.result sub.state.p.result))
10252+ ?: |((pending-tags tags.state.p.result sub.state.p.result) (itype-has-tag typ)) ~
10253+ ?: (unsupported-atom-vars typ caps.state.p.result binders.state.p.result sub.state.p.result) ~
10254+ =/ vars=(list @ud) (unique-type-vars (itype-vars typ))
10255+ ?: (gth (lent vars) 26) ~
10256+ =/ finalized=(unit (list iwprojection))
10257+ (width-finalize projections.state.p.result sub.state.p.result vars)
10258+ ?~ finalized ~
10259+ =? artifacts ?=(^ u.finalized)
10260+ [[name.d val.d typ sub.state.p.result (flop u.finalized)] artifacts]
10261+ =? checked |(?=(%gate -.val.d) ?=(%gate-wide -.val.d)) (~(put by checked) name.d d)
10262+ `[[[name.d [typ vars]] env] checked artifacts]
1024010263++ add-supported-defs-core
1024110264 |= [ds=(list def) all-defs=(list def) checked=(map term def) heads=(unit igate-prelude-evidence) env=ienv molds=imolds globals=(list term) externals=iexternals surs=(list term) mark-externals=imark-externals marks=(list term) jimports=ijimports reserved-molds=(list term) at=path prelude=(unit ifile) artifacts=(list iwidth-definition) index=(unit [at=path source=@t prelude=ifile shapes=(map term shape)])]
1024210265 ^- [env=ienv artifacts=(list iwidth-definition)]
@@ -10253,23 +10276,10 @@
1025310276 =. initial (reserve-def-caps val.d initial)
1025410277 =/ result=iresult (infer-fragment val.d env initial)
1025510278 =? result ?=(%| -.result) (infer-recursive-gate d env initial)
10256− ?: ?=(%| -.result) $(ds t.ds)
10257− =. state.p.result (promote-restricted-numbers state.p.result env)
10258− =. state.p.result (default-hole-numbers holes.state.p.result holes.initial state.p.result env !(itype-has-gate (itype-normalize typ.p.result sub.state.p.result)))
10259− ?^ (unresolved-hole holes.state.p.result holes.initial state.p.result) $(ds t.ds)
10260− ?: (constrained-capitals caps.state.p.result sub.state.p.result) $(ds t.ds)
10261− =/ typ=itype (default-decimals (itype-normalize typ.p.result sub.state.p.result))
10262− ?: |((pending-tags tags.state.p.result sub.state.p.result) (itype-has-tag typ)) $(ds t.ds)
10263− ?: (unsupported-atom-vars typ caps.state.p.result binders.state.p.result sub.state.p.result) $(ds t.ds)
10264− =/ vars=(list @ud) (unique-type-vars (itype-vars typ))
10265− ?: (gth (lent vars) 26) $(ds t.ds)
10266− =/ finalized=(unit (list iwprojection))
10267− (width-finalize projections.state.p.result sub.state.p.result vars)
10268− ?~ finalized $(ds t.ds)
10269− =? artifacts ?=(^ u.finalized)
10270− [[name.d val.d typ sub.state.p.result (flop u.finalized)] artifacts]
10271− =? checked |(?=(%gate -.val.d) ?=(%gate-wide -.val.d)) (~(put by checked) name.d d)
10272− $(ds t.ds, env [[name.d [typ vars]] env], checked checked)
10279+ =/ added=(unit iadded-definition)
10280+ (add-supported-result d result initial env checked artifacts)
10281+ ?~ added $(ds t.ds)
10282+ $(ds t.ds, env env.u.added, checked checked.u.added, artifacts artifacts.u.added)
1027310283::
1027410284++ resolve-groups-core
1027510285 |= [ds=(list def) molds=imolds globals=(list term) externals=iexternals surs=(list term) mark-externals=imark-externals marks=(list term) jimports=ijimports reserved-molds=(list term) at=path prelude=(unit ifile) index=(unit [at=path source=@t prelude=ifile shapes=(map term shape)])]
@@ -12466,6 +12476,19 @@
1246612476 ?. ?=([@ ~] names.e) ~
1246712477 `i.names.e
1246812478::
12479+++ program-main-args
12480+ |= d=def
12481+ ^- (unit (list expr))
12482+ ?. ?=(%value -.d) ~
12483+ |-
12484+ ?: ?=(%group -.val.d) $(val.d inner.val.d)
12485+ ?. ?=(%call -.val.d) ~
12486+ =/ head=(unit term) (program-main-wing head.val.d)
12487+ ?~ head ~
12488+ ?. =(%program u.head) ~
12489+ ?. ?=([* * * * ~] args.val.d) ~
12490+ `args.val.d
12491+::
1246912492++ program-main-parts
1247012493 |= d=def
1247112494 ^- (unit [init=term update=term view=term subs=term])
@@ -12510,6 +12533,141 @@
1251012533 * |
1251112534 ==
1251212535::
12536+++ program-transparent
12537+ |= e=expr
12538+ ^- expr
12539+ ?- -.e
12540+ %group $(e inner.e)
12541+ %cast $(e val.e)
12542+ %body ?~(defs.e $(e final.e) e)
12543+ * e
12544+ ==
12545+++ program-init-tuple-null
12546+ |= d=def
12547+ ^- ?
12548+ ?. ?=(%value -.d) |
12549+ ?. |(?=(%gate -.val.d) ?=(%gate-wide -.val.d)) |
12550+ =/ body=expr
12551+ ?- -.val.d
12552+ %gate body.val.d
12553+ %gate-wide body.val.d
12554+ * !!
12555+ ==
12556+ =. body (program-transparent body)
12557+ ?. ?=(%tuple -.body) |
12558+ ?~ items.body |
12559+ (program-empty-null val:(rear items.body))
12560+::
12561+:: Recover only the initializer of an already recognized direct program main.
12562+:: The derived call is evidence for an ordinary init/main retry, never an
12563+:: interface to publish on its own. Any failure returns the original resolution.
12564+++ program-init-context
12565+ |= [main=def parts=[init=term update=term view=term subs=term] ds=(list def) resolution=iresolved initial=istate]
12566+ ^- (unit iresolved)
12567+ =/ env=ienv env.resolution
12568+ ?^ (ienv-lookup %main env) ~
12569+ ?^ (ienv-lookup init.parts env) ~
12570+ =/ update=(unit ischeme) (ienv-lookup update.parts env)
12571+ =/ view=(unit ischeme) (ienv-lookup view.parts env)
12572+ =/ subs=(unit ischeme) (ienv-lookup subs.parts env)
12573+ ?~ update ~
12574+ ?~ view ~
12575+ ?~ subs ~
12576+ =/ inits=(list def)
12577+ (skim ds |=(d=def &(?=(%value -.d) =(init.parts name.d))))
12578+ ?. ?=([* ~] inits) ~
12579+ =/ init-def=def i.inits
12580+ ?> ?=(%value -.init-def)
12581+ ?. (program-init-tuple-null init-def) ~
12582+ =/ args=(unit (list expr)) (program-main-args main)
12583+ ?~ args ~
12584+ ?~ prelude.initial ~
12585+ =/ program=(unit ischeme) (ienv-lookup %program values.u.prelude.initial)
12586+ ?~ program ~
12587+ =/ heads=(unit igate-prelude-evidence) (direct-prelude-evidence ds prelude.initial)
12588+ ?~ heads ~
12589+ =/ written=(unit def) (~(get by gates.u.heads) %program)
12590+ ?~ written ~
12591+ =/ written-sample=(unit sample) (direct-gate-sample u.written)
12592+ ?~ written-sample ~
12593+ ?. ?=(%cell -.u.written-sample) ~
12594+ ?~ items.u.written-sample ~
12595+ =/ checked=(map term def) (direct-checked-gates ds env)
12596+ =/ templates=ienv
12597+ (turn molds.u.prelude.initial |=(x=[name=term template=ischeme] [name.x template.x]))
12598+ =/ scanned=(unit dgscan)
12599+ (dg-all-env-ids (weld env (weld values.u.prelude.initial templates)) 32.768 ~)
12600+ ?~ scanned ~
12601+ =. next.initial (dg-next-id ids.u.scanned 0)
12602+ =/ instance=[typ=itype state=istate] (instantiate-scheme u.program initial)
12603+ ?. ?=(%fn -.typ.instance) ~
12604+ ?. ?=(%cell -.sample.typ.instance) ~
12605+ =/ expected-init=(unit itype)
12606+ (direct-slot-body hed.sample.typ.instance (direct-sample-name i.items.u.written-sample))
12607+ ?~ expected-init ~
12608+ ?. ?=(%fn -.u.expected-init) ~
12609+ ?. ?=(%var -.sample.u.expected-init) ~
12610+ =/ data=@ud id.sample.u.expected-init
12611+ =/ init-var=itype [%var next.state.instance]
12612+ =/ st=istate state.instance(next +(next.state.instance))
12613+ =/ up=[typ=itype state=istate] (instantiate-scheme u.update st)
12614+ =/ vw=[typ=itype state=istate] (instantiate-scheme u.view state.up)
12615+ =/ sb=[typ=itype state=istate] (instantiate-scheme u.subs state.vw)
12616+ =. st state.sb(gate-defs checked, head-evidence heads)
12617+ =/ actual=itype [%cell init-var [%cell typ.up [%cell typ.vw typ.sb]]]
12618+ =/ fit=ifit
12619+ (direct-positional-fit u.args items.u.written-sample actual sample.typ.instance env st sub.st next.st)
12620+ ?. ?=(%ok -.fit) ~
12621+ =/ actual-final=itype (itype-normalize actual sub.fit)
12622+ =/ expected-final=itype (itype-normalize sample.typ.instance sub.fit)
12623+ =/ open=(list @ud)
12624+ (unique-type-vars (weld (itype-vars actual-final) (itype-vars expected-final)))
12625+ ?. (levy open |=(n=@ud =(data n))) ~
12626+ ?: |((itype-has-tag actual-final) (itype-has-tag expected-final)) ~
12627+ =/ replay=ifit
12628+ (direct-positional-fit u.args items.u.written-sample actual sample.typ.instance env st sub.fit next.fit)
12629+ ?. ?=(%ok -.replay) ~
12630+ ?. &(=(sub.fit sub.replay) =(next.fit next.replay)) ~
12631+ =/ expected=itype (itype-normalize u.expected-init sub.fit)
12632+ ?. ?=(%fn -.expected) ~
12633+ =. st st(sub sub.fit, next next.fit)
12634+ =/ init-state=istate (reserve-def-caps val.init-def st)
12635+ =/ inferred=iresult (infer-fragment val.init-def env init-state)
12636+ =? inferred ?=(%| -.inferred) (infer-recursive-gate init-def env init-state)
12637+ ?: ?=(%| -.inferred) ~
12638+ ?. ?=(%fn -.typ.p.inferred) ~
12639+ =/ data-fit=ifit
12640+ (itype-fit sample.typ.p.inferred sample.expected sub.state.p.inferred next.state.p.inferred)
12641+ ?. ?=(%ok -.data-fit) ~
12642+ =/ closed=itype (itype-normalize expected sub.data-fit)
12643+ ?. ?=(%fn -.closed) ~
12644+ :: Resolve the ordinary tuple cast first, then check the complete gate.
12645+ :: General function fit remains unchanged; only this recovered candidate
12646+ :: receives the derived result context before its whole-type cast fit.
12647+ =/ product-fit=ifit
12648+ (itype-fit product.typ.p.inferred product.closed sub.data-fit next.data-fit)
12649+ ?. ?=(%ok -.product-fit) ~
12650+ =/ product=itype
12651+ (itype-merge-faces product.typ.p.inferred product.closed sub.product-fit)
12652+ =/ actual-cast=itype [%fn sample.typ.p.inferred product]
12653+ =/ cast=ifit
12654+ (itype-fit actual-cast closed sub.product-fit next.product-fit)
12655+ ?. ?=(%ok -.cast) ~
12656+ =/ merged=itype (itype-merge-faces actual-cast closed sub.cast)
12657+ =/ adjusted=iresult
12658+ [%& merged state.p.inferred(sub sub.cast, next next.cast)]
12659+ =/ added=(unit iadded-definition)
12660+ (add-supported-result init-def adjusted init-state env checked artifacts.resolution)
12661+ ?~ added ~
12662+ =/ recovered=(unit ischeme) (ienv-lookup init.parts env.u.added)
12663+ ?~ recovered ~
12664+ ?. ?=(~ vars.u.recovered) ~
12665+ ?. ?=(~ (itype-vars typ.u.recovered)) ~
12666+ =/ completed=[env=ienv artifacts=(list iwidth-definition)]
12667+ (add-supported-defs-core ~[main] ds checked.u.added heads env.u.added molds.initial globals.initial externals.initial surs.initial mark-externals.initial marks.initial jimports.initial reserved-molds.initial at.initial prelude.initial artifacts.u.added grec-index.initial)
12668+ ?~ (ienv-lookup %main env.completed) ~
12669+ `[env.completed grouped.resolution artifacts.completed]
12670+::
1251312671++ program-main-closed
1251412672 |= [parts=[init=term update=term view=term subs=term] ds=(list def) env=ienv prelude=(unit ifile)]
1251512673 ^- ?
@@ -12627,6 +12785,13 @@
1262712785 (resolve-groups defs.mod molds globals externals surs mark-externals marks resolved.p.js reserved at prelude)
1262812786 (resolve-groups-core defs.mod molds globals externals surs mark-externals marks resolved.p.js reserved at prelude index)
1262912787 =/ env=ienv env.resolution
12788+ =/ recovered=(unit iresolved)
12789+ ?~ main ~
12790+ ?~ main-parts ~
12791+ (program-init-context u.main u.main-parts defs.mod resolution state)
12792+ =. env ?~(recovered env env.u.recovered)
12793+ =. artifacts.resolution
12794+ ?~(recovered artifacts.resolution artifacts.u.recovered)
1263012795 =. env
1263112796 ?: (module-values-complete defs.mod env) env
1263212797 ?: |(?=([%ui %stores *] at) ?=(^ imports.mod) ?=(^ prelude) ?=(^ prelude-values) ?=(^ prelude-molds)) env
tests/lib/jibb-program-init.hoonnew file+135
@@ -0,0 +1,135 @@
1+:: Transactional direct-program initializer context, without UI execution.
2+/+ jibb
3+/* prelude %jibb /lib/jibb/prelude/jibb
4+/* internal %jibb /lib/jibb/internal/jibb
5+/* initializer %txt /tests/type-program-main/initializer/txt
6+/* initializer-consumer %txt /tests/type-program-main/initializer-consumer/txt
7+/* initializer-unfaced %txt /tests/type-program-main/initializer-unfaced/txt
8+/* initializer-error %txt /tests/type-program-main/initializer-error/txt
9+/* initializer-earlier %txt /tests/type-program-main/initializer-earlier/txt
10+/* initializer-model %txt /tests/type-program-main/initializer-model/txt
11+/* initializer-message %txt /tests/type-program-main/initializer-message/txt
12+/* initializer-generic %txt /tests/type-program-main/initializer-generic/txt
13+/* initializer-missing %txt /tests/type-program-main/initializer-missing/txt
14+/* initializer-tag %txt /tests/type-program-main/initializer-tag/txt
15+/* initializer-no-main %txt /tests/type-program-main/initializer-no-main/txt
16+/* initializer-subs %txt /tests/type-program-main/initializer-subs/txt
17+|%
18+++ text
19+ |= lines=wain
20+ ^- @t
21+ (rap 3 (turn lines |=(line=@t (cat 3 line '\0a'))))
22+::
23+++ program-init-prepared
24+ |= [public=@t private=@t]
25+ ^- (unit iprelude:jibb)
26+ =/ extra=@t '\0a+$ element |$ [M] [%node item=M]\0a+$ program |$ [D X M]\0a [init=$-(D X) update=$-([M X] [X (list (cmd M))]) view=$-(X (element M)) subs=$-(X (list (sub M)))]\0a++ program |= [init=$-(D X) update=$-([M X] [X (list (cmd M))]) view=$-(X (element M)) subs=$-(X (list (sub M)))]\0a ^- (program D X M) [init update view subs]\0a'
27+ (prepare-prelude-with-internal:jibb (cat 3 public extra) private)
28+::
29+++ program-init-detailed
30+ |= [at=path src=@t modules=jbundle:jibb prepared=iprelude:jibb]
31+ ^- jfile-detailed-result:jibb
32+ (infer-jibb-file-detailed:jibb at src ~ ~ modules ~[at] values.prepared molds.prepared ~ `iface.prepared)
33+::
34+++ program-init-legacy
35+ |= [at=path src=@t modules=jbundle:jibb prepared=iprelude:jibb]
36+ ^- jfile-result:jibb
37+ (infer-jibb-file:jibb at src ~ ~ modules ~[at] values.prepared molds.prepared ~ `iface.prepared)
38+::
39+++ program-init-positive
40+ |= [at=path src=@t downstream=(unit @t) prepared=iprelude:jibb]
41+ ^- (list tape)
42+ =/ detailed=jfile-detailed-result:jibb (program-init-detailed at src ~ prepared)
43+ ?: ?=(%| -.detailed) ~["contextual direct program init was rejected"]
44+ =/ checked=ifile:jibb iface.p.detailed
45+ =/ legacy=jfile-result:jibb (program-init-legacy at src ~ prepared)
46+ ?: ?=(%| -.legacy) ~["legacy adapter rejected contextual init"]
47+ ?. =(values.checked values.iface.p.legacy) ~["contextual structured and legacy values differ"]
48+ =/ init=(unit ischeme:jibb) (ienv-lookup:jibb %init values.checked)
49+ =/ main=(unit ischeme:jibb) (ienv-lookup:jibb %main values.checked)
50+ ?~ init ~["contextual init interface is missing"]
51+ ?~ main ~["contextual main interface is missing"]
52+ ?. ?=(~ vars.u.init) ~["contextual init interface is not closed"]
53+ =/ expected=tape "$-((list @t) [xs=(list @t) [draft=@t error=(unit @t)]])"
54+ ?. =((show-itype:jibb typ.u.init ~) expected) ~["contextual init interface type differs"]
55+ ?. ?=(~ vars.u.main) ~["contextual main interface is not closed"]
56+ =/ fields=(list itype:jibb) (record-fields-named:jibb %init typ.u.main ~)
57+ ?. =(fields ~[typ.u.init]) ~["contextual main initializer field differs"]
58+ ?~ source.checked ~["contextual program source evidence is missing"]
59+ ?. =(u.source.checked src) ~["contextual program source evidence differs"]
60+ ?~ graph.checked ~["contextual program request graph is missing"]
61+ ?~ downstream ~
62+ =/ consumer-at=path /tests/program-main/initializer-consumer/jibb
63+ =/ imported=jfile-detailed-result:jibb
64+ (program-init-detailed consumer-at u.downstream ~[[at src]] prepared)
65+ ?: ?=(%| -.imported) ~["downstream contextual initializer import was rejected"]
66+ =/ use=(unit ischeme:jibb) (ienv-lookup:jibb %imported values.iface.p.imported)
67+ ?~ use ~["downstream contextual initializer is missing"]
68+ ?. =((show-itype:jibb typ.u.use ~) expected) ~["downstream contextual initializer type differs"]
69+ ?. =(u.use u.init) ~["downstream contextual initializer scheme differs"]
70+ ~
71+::
72+++ program-init-negative
73+ |= [label=@t at=path src=@t boundary=@t prepared=iprelude:jibb]
74+ ^- (list tape)
75+ =/ detailed=jfile-detailed-result:jibb (program-init-detailed at src ~ prepared)
76+ ?: ?=(%& -.detailed) ~[(weld (trip label) ": structured check accepted")]
77+ ?. ?=(~ overlap.p.detailed) ~[(weld (trip label) ": unexpected overlap evidence")]
78+ ?. =(message.p.detailed boundary) ~[(weld (trip label) ": rejected at the wrong boundary")]
79+ =/ legacy=jfile-result:jibb (program-init-legacy at src ~ prepared)
80+ ?: ?=(%& -.legacy) ~[(weld (trip label) ": legacy check accepted")]
81+ ?. =(p.legacy boundary) ~[(weld (trip label) ": legacy boundary differs")]
82+ ~
83+::
84+++ program-init-negative-cases
85+ |= [cases=(list [label=@t at=path src=@t boundary=@t]) prepared=iprelude:jibb]
86+ ^- (list tape)
87+ ?~ cases ~
88+ %+ weld
89+ (program-init-negative label.i.cases at.i.cases src.i.cases boundary.i.cases prepared)
90+ $(cases t.cases)
91+::
92+++ program-init-errors
93+ |= [public=@t private=@t island=@t downstream=@t unfaced=@t negatives=(list @t)]
94+ ^- (list tape)
95+ =/ prepared=(unit iprelude:jibb) (program-init-prepared public private)
96+ ?~ prepared ~["synthetic program prelude did not prepare for contextual init"]
97+ =/ at=path /tests/program-main/initializer/jibb
98+ =/ positives=(list tape)
99+ %+ weld
100+ (program-init-positive at island `downstream u.prepared)
101+ (program-init-positive /tests/program-main/initializer-unfaced/jibb unfaced ~ u.prepared)
102+ ?. =(9 (lent negatives)) ~["contextual negative fixture count differs"]
103+ =/ definitions=@t (crip "module definitions need unsupported inference: {(spud at)}")
104+ =/ cases=(list [label=@t at=path src=@t boundary=@t])
105+ :~ ['incompatible error field' at (snag 0 negatives) definitions]
106+ ['earlier tuple mismatch' at (snag 1 negatives) definitions]
107+ ['inconsistent model' at (snag 2 negatives) definitions]
108+ ['inconsistent message' at (snag 3 negatives) definitions]
109+ ['missing/generic data sample' at (snag 4 negatives) definitions]
110+ ['missing initializer' at (snag 5 negatives) definitions]
111+ ['unrelated pending tag' at (snag 6 negatives) definitions]
112+ ['non-main same source' at (snag 7 negatives) definitions]
113+ ['init and subs unresolved' at (snag 8 negatives) definitions]
114+ :- 'API path'
115+ [/ui/api/program-main/jibb island 'module kind needs unsupported inference: /ui/api/program-main/jibb']
116+ :- 'store path'
117+ [/ui/stores/program-main/jibb island 'module kind needs unsupported inference: /ui/stores/program-main/jibb']
118+ ==
119+ (weld positives (program-init-negative-cases cases u.prepared))
120+::
121+++ test-program-init
122+ ^- tang
123+ =/ negatives=(list @t)
124+ :~ (text initializer-error)
125+ (text initializer-earlier)
126+ (text initializer-model)
127+ (text initializer-message)
128+ (text initializer-generic)
129+ (text initializer-missing)
130+ (text initializer-tag)
131+ (text initializer-no-main)
132+ (text initializer-subs)
133+ ==
134+ (turn (program-init-errors prelude internal (text initializer) (text initializer-consumer) (text initializer-unfaced) negatives) |=(reason=tape leaf+reason))
135+--
tests/type-program-main/initializer-consumer.txtnew file+2
@@ -0,0 +1,2 @@
1+/= island /tests/program-main/initializer
2+++ imported init.main.island
tests/type-program-main/initializer-earlier.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts ~zod ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-error.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=@t]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-generic.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-message.txtnew file+13
@@ -0,0 +1,13 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5++$ other
6+ %start
7+ %stop
8+++ main (program init update view subs)
9+++ init |= ts=(list @t) [ts "" ~]
10+++ update |= [msg=other state=model]
11+ ^- [model (list (cmd other))] [state ~]
12+++ view |= state=model ^- (element message) [%node %start]
13+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-missing.txtnew file+9
@@ -0,0 +1,9 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ update |= [msg=message state=model]
7+ ^- [model (list (cmd message))] [state ~]
8+++ view |= state=model ^- (element message) [%node %start]
9+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-model.txtnew file+11
@@ -0,0 +1,11 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ other [xs=(list @t) draft=@t error=(unit @t) extra=@ud]
3++$ message
4+ %start
5+ %stop
6+++ main (program init update view subs)
7+++ init |= ts=(list @t) [ts "" ~]
8+++ update |= [msg=message state=other]
9+ ^- [other (list (cmd message))] [state ~]
10+++ view |= state=model ^- (element message) [%node %start]
11+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-no-main.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ direct (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer-subs.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state ~
tests/type-program-main/initializer-tag.txtnew file+13
@@ -0,0 +1,13 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
11+++ orphan
12+ ~& %unrelated
13+ 0
tests/type-program-main/initializer-unfaced.txtnew file+10
@@ -0,0 +1,10 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= input=[message model]
8+ ^- [model (list (cmd message))] [[~ "" ~] ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model ^- (list (sub message)) ~
tests/type-program-main/initializer.txtnew file+12
@@ -0,0 +1,12 @@
1++$ model [xs=(list @t) draft=@t error=(unit @t)]
2++$ message
3+ %start
4+ %stop
5+++ main (program init update view subs)
6+++ init |= ts=(list @t) [ts "" ~]
7+++ update |= [msg=message state=model]
8+ ^- [model (list (cmd message))] [state ~]
9+++ view |= state=model ^- (element message) [%node %start]
10+++ subs |= state=model
11+ ^- (list (sub message))
12+ (~)
tools/testdata/ship-selftest+6−1
@@ -325,6 +325,7 @@ def generator_checks(tmp):
325325 'tests/lib/jibb-direct-gate-guards.hoon',
326326 'tests/lib/jibb-direct-gate-source.hoon',
327327 'tests/lib/jibb-program-main.hoon',
328+ 'tests/lib/jibb-program-init.hoon',
328329 'tests/lib/jibb-todo-api.hoon',
329330 'tests/lib/jibb-todo-partial.hoon',
330331 'tests/lib/jibb-constructor-fit-guards.hoon',
@@ -399,7 +400,11 @@ def generator_checks(tmp):
399400 check('program main sources and shared fixture are assembled',
400401 {f'tests/type-program-main/{name}.txt' for name in (
401402 'source', 'consumer', 'gate-model', 'gate-data', 'nonempty-subs',
402− 'mismatched-model', 'arbitrary-main', 'alias-main', 'open-message')} <= set(normal.files))
403+ 'mismatched-model', 'arbitrary-main', 'alias-main', 'open-message',
404+ 'initializer', 'initializer-consumer', 'initializer-unfaced',
405+ 'initializer-error', 'initializer-earlier', 'initializer-model',
406+ 'initializer-message', 'initializer-generic', 'initializer-missing',
407+ 'initializer-tag', 'initializer-no-main', 'initializer-subs')} <= set(normal.files))
403408 check('todo partial golden and reused sources are assembled',
404409 {'tests/type-todo-partial/expected.txt', 'tests/type-gate-free/todo.txt',
405410 'tests/type-api-paths/api.txt'} <= set(normal.files))
tools/type-program-init-testnew file+49
@@ -0,0 +1,49 @@
1+#!/usr/bin/env python3
2+"""Run transactional direct-program initializer context checks under eval."""
3+from pathlib import Path
4+import argparse
5+import subprocess
6+import sys
7+import tempfile
8+
9+root = Path(__file__).resolve().parent.parent
10+parser = argparse.ArgumentParser(description=__doc__)
11+parser.add_argument('--lib', default=str(root / 'jibb-dev/lib/jibb.hoon'))
12+args = parser.parse_args()
13+
14+def body(name):
15+ text = (root / 'tests/lib' / name).read_text()
16+ return text[text.index('\n|%\n') + 1:]
17+
18+names = ('initializer', 'initializer-consumer', 'initializer-unfaced',
19+ 'initializer-error', 'initializer-earlier', 'initializer-model',
20+ 'initializer-message', 'initializer-generic', 'initializer-missing',
21+ 'initializer-tag', 'initializer-no-main', 'initializer-subs')
22+expression = ('=/ jibb .\n=/ prelude=@t prelude-text\n'
23+ '=/ internal=@t internal-text\n' +
24+ ''.join('=/ ' + name + '=wain (to-wain:format ' + name + '-text)\n'
25+ for name in names) +
26+ '=/ suite\n' + body('jibb-program-init.hoon') +
27+ '\n=/ negatives=(list @t)\n :~ (text:suite initializer-error)\n'
28+ ' (text:suite initializer-earlier)\n (text:suite initializer-model)\n'
29+ ' (text:suite initializer-message)\n (text:suite initializer-generic)\n'
30+ ' (text:suite initializer-missing)\n (text:suite initializer-tag)\n'
31+ ' (text:suite initializer-no-main)\n (text:suite initializer-subs)\n ==\n'
32+ '=/ failures=(list tape) (program-init-errors:suite prelude internal (text:suite initializer) (text:suite initializer-consumer) (text:suite initializer-unfaced) negatives)\n'
33+ '^- @t\n?~ failures \'program initializer checks passed\'\n'
34+ '(of-wain:format (turn failures crip))\n')
35+with tempfile.TemporaryDirectory(prefix='jibb-program-init-') as tmp:
36+ program = Path(tmp) / 'program-init.hoon'
37+ program.write_text(expression)
38+ command = [str(root / 'tools/hoon'), '--lib', args.lib, '--raw', '-f', str(program),
39+ '--cord', 'prelude-text=' + str(root / 'jibb-dev/lib/jibb/prelude.jibb'),
40+ '--cord', 'internal-text=' + str(root / 'jibb-dev/lib/jibb/internal.jibb')]
41+ for name in names:
42+ command += ['--cord', name + '-text=' + str(root / 'tests/type-program-main' / (name + '.txt'))]
43+ result = subprocess.run(command, stdout=subprocess.PIPE)
44+if result.returncode:
45+ sys.exit(result.returncode)
46+if result.stdout != b'program initializer checks passed\n':
47+ sys.stderr.buffer.write(result.stdout)
48+ sys.exit(1)
49+print('transactional direct-program initializer context guards passed')