VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author
Reading time
69 min
Rendered
Read aloud
never

PROP-054 — The vibe lifecycle and the extension machine

01Related: PROP-009 (install orchestration — becomes phase 2 of the default lifecycle), PROP-011 (skip-when-fresh — §9 revises its materialise step from slot-wipe to per-file diff), PROP-020 (the two install hooks whose ##REJ-GENERAL-LIFECYCLE deferral this document un-defers; its trust gate is adopted whole), PROP-022 (slot reset semantics — §9 refines them), PROP-024 (code in packages; §8 amends its §2.3 build-output home), PROP-025 §3–§4 ([[binary]] build-in-slot — the precedent §8 generalises), PROP-035 (the compile pipeline §7 attaches extension points to), PROP-045 (transformed-slot identity is absorbed into the per-file slot record), PROP-053 (verb chaining — §4.4 is the "own semantics ruling" its ##CHAIN-ONLY-INSTALL demanded before any wider grammar).

1. The owner's mandate

02The owner's words (2026-08-24), verbatim, abridged only of the courtesy framing: «Я хочу продумать аналог Lifecycle как то сделано в Maven. И сделать хуки в этот Lifecycle как это сделано в Maven и Babel. […] Ложится ли наш Lifecycle на Maven Lifecycle? Каких фаз у нас еще нет? Я хочу чтобы движок лайфсайклов был встроен прямо в Vibe, и так же как в Maven если мы делаем стадию более высокого уровня, то все предыдущие тоже выполняются. Но надо понимать разницу между нашим install и их install. Их install это компиляция, наш - это материализация в vibedeps. Поэтому наш install запускается в самом начале, первой фазой (при этом он должен быть строго инкрементальным - если vibe.toml не поменялся, инсталлировать пакеты не нужно). Наш аналог стадии install будет называться build и create. На стадии build будут собирать алгоритмически собираемые исходники (например, Rust). На стадии create - в этот момент будет активироваться какой-нибудь внешний агент или vibe-llm (если запущено из командной строки) либо если vibe вызван из агента - будут нашим хитрым механизмом передаваться промты для обеспечения агентного режима. Собственно, vibe build это довольно понятная детерминированная стадия, а вот create - это долго и непонятно, поэтому она одна из последних (но до package и deploy по аналогии с Maven). На этапе package мы соответственно будем собирать что-то типа статически собираемых скиллов, плагинов для Claude, инсталляторов для локально запускаемых утилит, и так далее. Deploy - это обычно раскатка на сервера. Я хочу сразу запроектировать две вещи. Во-первых, стандартный ритуал вызова стандартных методов как в Maven. Второе - так же как в Maven, возможность сделать пакет-плагин и добавить новую функциональность в этот ритуал.»

03The owner's second ruling (2026-08-25), verbatim, on reviewing the first draft: «Я бы хотел, чтобы плагины компилятора имели полный доступ к IR пакетов и могли его изменять как хочешь. То есть, чтобы это был полноценное компиляторное расширение - так же как ты можешь писать новые оптимизации на бэкенде и новые фронтенды для LLVM. Да, это небезопасно, поэтому я предлагаю сделать отдельный флаг в vibe.toml который явно покажет, что плагин попросил доступ к такому низкоуровневому API. Я хочу чтобы пользователи могли прямо достроить компилятор поверх этого. Вероятно, под это нужно отрефакторить переходы в нашем IR и сделать всё высоко композируемым.» This ruling shapes §7's two-tier model, §7.5's IR refactor, and the pass tier's compiler_internals flag.

04The owner's third ruling (2026-08-25), verbatim: «Я не хочу консентов. Если пользователь установил пакет - это само по себе уже консент. Есть множество компиляторов от Gradle и Webpack до Clang которые поддерживают всевозможные нативные расширения. Где ты видел, чтобы пользователь сидел и подтверждал эти разрешения? На модификацию графа? Да обычный пользователь вообще не разбирается, как это всё внутри работает, он пришел за работающими промтами. Что полезно сделать: полную прозрачность observability, чтобы отладчики, сканеры, специалисты по internals мгновенно могли понять, что там происходит.» And, on binary distribution: «…если кто-то захочет создать полностью закрытый плагин и не поставлять исходников, ему придется скомпилировать этот плагин подо все существующие в природе версии Rust, что кажется очень сложной задачей» — the fear this document answers by NOT being ABI-bound to Rust (§8.1: the C-ABI/VST posture, and §8.2's prebuilt closed-source form). These rulings shape §3.3's installation-is-consent law, §3.5's observability surface, and closed ##OPEN-PHASE-NAMES (the nine-phase table stands as drafted) and ##OPEN-NATIVE-TRUST.

05Continued, verbatim: «В качестве тестового сценария обработки стадии vibevm lifecycle я предлагаю какой-то механизм, который может определить плагин в своем манифесте, который программно будет что-то печатать - например, название стадии. […] Сейчас стоит посмотреть на то, какая информация попадает в обработку стадий в Maven и сделать это. Вторая иерархия мест расширения - это процесс компиляции, который аналогичен Babel. […] В данном случае копипастить бессмысленно с Babel не нужно, нужно только в общих чертах делать не хуже. В качестве тестового сценария плагина компилятора я предлагаю какой-нибудь простейший ТЕСТОВЫЙ алгоритм компрессии, например - в результирующем XML удалить все пробелы и переносы строк между тэгами […]. На самом деле, убирать пробелы и переносы - это плохая идея […], но это хороший пример для тестирования механики. В дальнейшем мы например, сделаем туда плагин компрессии по Caveman […]. Почему я рассказываю тебе про обе эти системы вместе? Потому что их дизайн должен быть сделан похоже и синергично, чтобы у пользователей не было ощущения разброда и нелогичности в разных форматах расширения VibeVM. Они должны быть взаимодополняющими, удобными, понятными. Также важная техническая деталь. Поскольку мы пишем не на Java или JavaScript как Maven или Babel, мы не можем просто так легко обрабатывать стадии скриптами. […] Должен быть еще один и самый продвинутый способ - что для lifecycle что для компилятора - это вызов нативного Rust кода. Я предлагаю систему, в которой библиотеки могут поставить код, который соберется в динамическую библиотеку, которую vibevm подхватит. Такие библиотеки, могут быть специфичны для проекта, поэтому собранные расширения нужно собирать inplace прямо внутри vibedeps либо внутри packages (если это "наш" пакет). Крайне важно, чтобы vibevm делала install инкрементально, потому что иначе она рискует стереть уже собранные нативные расширения […]. Нужна ревизия инкрементальности утилиты install, чтобы она не стирала всё перед установкой пакета, а накатывала только реально обновленную разницу. Эта инкрементальность - супер важный кусок, без которого всё будет слишком сильно тормозить, чтобы этим можно было пользоваться.»

1.1 Vocabulary admitted by the lifecycle mandate

06Owner-approved vocabulary supersedes the prototype's blanket Maven-word ban. lifecycle and phase are now VibeVM's own public and internal terms, with the exact meanings in §4: two composed lifecycles and nine default phases. goal remains absent from VibeVM grammar — the machine nouns are contribution (one [[extension]] declaration) and execution (one scheduled run). plugin is a human umbrella only when precision requires it: a package-supplied extension/mechanism implementation, compiler plugin, or an external ecosystem artifact such as Agent Plugin. It is never a persisted identity, never a synonym for contribution/execution/handler, and never a seventh installable kind. Machine grammar uses package → extension point/mechanism → contribution → execution → handler/provider. The closed installable-kind register remains flow | feat | stack | tool | mcp | lang; this ruling widens vocabulary, not that register.

07Maven/Babel/LLVM terms outside the admitted set remain prior-art language, not an API template: no Maven goal, mojo, packaging, site lifecycle or 23-phase spelling enters VibeVM merely by analogy. The project studies mechanisms, then defines its own exact grammar at addressable anchors.

2. Prior art, and where we stand against it

2.1 The Maven model — what we adopt and what we do not

08Maven's model, from the primary source (the lifecycle introduction, read 2026-08-24): three built-in lifecycles — default (23 phases: validate, initialize, generate-sources … compile … test … package … verify, install, deploy), clean (pre-clean, clean, post-clean), site. Invoking a phase executes every prior phase of that lifecycle plus the phase itself; mvn clean deploy runs two lifecycles in sequence. A phase is a slot; a plugin goal is a task bound to a slot; «if a build phase has no goals bound to it, that build phase will not execute» — an empty phase is a free no-op. The <packaging> element selects a default goal-to-phase binding table (jar binds compiler:compile to compile, surefire:test to test, jar:jar to package…; pom packaging binds almost nothing). Plugins add goals via <executions> with per-execution <phase>, <configuration> and an id; multiple goals on one phase run in POM declaration order, packaging-bound goals first.

  • 09Adopted: the phase line as the user-facing ritual (one short vocabulary that builds any vibe project); invoke-runs-all-priors; clean as a separate lifecycle composed by vibe clean <phase>; empty-phase-is-a-no-op; packaging-style default bindings selected by the manifest (§4.5); plugin executions with per-execution phase, id and config (§6).
  • Not adopted: the 23-phase granularity (our default lifecycle has nine phases — the hyphenated pre-*/process-* intermediate slots are exactly the ones Maven itself says are «not usually directly called from the command line», and we start without them; a future phase can be inserted behind its own ruling because phase order is a table, not an enum in stone); the site lifecycle (our "site" — the boot lane and generated docs — is already the product of install/compile, not a third lifecycle); Maven's unconditional re-execution (§4.3 makes every phase skip-when-fresh — the Gradle/Bazel property, and the owner's hard requirement).

2.2 The Babel model — what we adopt and what we do not

10Babel's model, from the primary source (the plugins reference, read 2026-08-24): compilation is parse → transform → generate; plugins are functions returning a visitor keyed by AST node type (Identifier(path) { … }), enabled by listing them in configuration; options ride as ["plugin-name", { …opts }]; ordering is «plugins run before presets; plugin ordering is first to last; preset ordering is reversed»; a transform plugin auto-enables its syntax plugin; presets are named plugin groups.

  • 11Adopted: transforms as declared, configured, ordered pipeline entries over the compiler's own IR; per-plugin options in the declaration; presets as named groups (our kind-presets, §4.5, serve both hierarchies).
  • Not adopted (per the mandate — "не хуже в общих чертах, без копипаста"): the reversed-preset ordering rule (one rule for everything: declaration order, §3.4 — Babel's reversal is a compat artefact users must memorise); the per-node-type visitor as the FIRST-wave external plugin API — our tier-1 transform stages are document-grained (§7.2), because our compiler already runs its own visitors internally (qualify, the absorbed filter) and a document-grained transform crosses the native ABI in one call instead of thousands (§8.5); full-depth mutation is served by the pass tier (§7.4) as whole-IR-in/whole-IR-out, not by an interactive per-node visitor API — that interactive surface stays deferred behind this anchor.

2.3 The LLVM/MLIR model — the pass tier's ancestry

12The 2026-08-25 ruling names a third ancestor: LLVM — where an extension is not a consumer of fixed hook points but a pass over the compiler's own IR, ordered by a pass manager among the built-in passes, with custom frontends lowering new source languages INTO the IR and custom backends lowering it OUT. The closest structural match for us is actually MLIR's multi-level shape: our compilation is a progressive lowering through named representations (§7.4.1), transforms run within a level, lowerings step between levels, and a verifier runs between passes so a broken invariant fails at its author, not three passes later.

13Adopted (tier 2, §7.4): the pass as the unit of extension; the pass manager with position-by-name (after/before/replace a built-in pass); frontends and backends as lowering passes anyone can add; the inter-pass verifier. Not adopted: LLVM's in-process C++ API surface as the plugin contract (no stable Rust ABI exists — §8.1's law stands; a pass crosses the C ABI as whole-IR-in / whole-IR-out JSON, §7.4.4) and LLVM's unversioned IR churn (our IR wire schema is versioned and gated, §7.4.5).

2.4 The Webpack model — the audit that found three gaps

14Webpack (owner-supplied source, 2026-08-25): an event-driven plugin-based compiler — a lifecycle of hook events (compile → resolve → parse → bundle → post-process) where even core functionality ships as default plugins on the same hooks; loaders transform individual files (selected per-file by test/include/exclude rules, chained) while plugins work on the whole bundle at the end; the dependency graph is built by recursive resolution from an entry, topologically sorted, merged, then tree-shaken and minified; watch mode and persistent caching make rebuilds incremental; stats/bundle-analyzer attribute bundle size to modules.

  • 15Already ours, checked point by point: core-as-plugins ⇔ §7.5's «built-ins are not privileged»; loaders-vs-plugins ⇔ tier 1's per-document stages (compile:source/document) versus whole-lane stages (compile:lane/emitted) — one machine where Webpack has two vocabularies; entry ⇔ the boot-snippet seed; import-order/cycles ⇔ the topo order and three-colour cycle walker PROP-035 already ships; code-splitting/lazy chunks ⇔ static-vs-dynamic lanes plus lazy-pull subskills, already the loading model; minification ⇔ §10.2; stats/profiling ⇔ §3.5. Tree shaking we have by construction, not as an optimisation: the closure is computed FROM the seed over authored edges, so unreachable content never enters the lane at all — Webpack prunes after the fact what our loading semantics never admits.
  • Three things the audit found missing, now specified: per-contribution SELECTORS (the loader test-rule genre — §3.2 ##CONTRIB-SELECTOR); the compile cache the refactor must not preclude (the persistent-cache/Turbopack lesson — §7.5 ##COMPILE-CACHE-READY); and the lane analyzer (the bundle-analyzer genre bound to our tokenomics law — §3.5 ##OBS-LANE-ANALYZER).

2.5 Does our lifecycle map onto Maven's? Which phases are missing?

16The owner's direct question, answered. It maps — with three systematic shifts, not one-to-one. Shift 1: Maven's dependency resolution is not a phase (it happens before/lazily); ours IS the first real phase, because our target/-equivalent is the materialised prompt world itself. Shift 2: Maven's single compile splits in two for us — deterministic build and agentic create — because we have a producer Maven does not: the LLM. Shift 3: Maven re-runs everything; every phase of ours must be skip-when-fresh or the agentic/native costs make the ritual unusable.

17The correspondence, phase by phase (their name → ours → what exists today):

18Maven               vibe          exists today?
validate            validate      YES — cheap manifest/world/extension preflight; `vibe check`
                                  remains the richer standalone diagnostic verb
(resolution, no     install       YES — PROP-009/011 plus per-file diff materialisation,
 phase in Maven)                  two world epochs and slot lifecycle contributions
generate-sources    generate      YES — phase/extension point; empty unless presets/plugins bind it
compile             build         YES — phase/extension point and stack/package binary presets
test                test          YES — phase/extension point and declared stack test runners
(no equivalent)     create        YES — strict selected-world CLI provider execution or durable
                                  hosted park/resume; no contribution stays algorithmic
verify              verify        phase exists; R7.5 exact evidence reconciliation is current work
package             package       phase exists; project skill binding + target grammar landed,
                                  general portable artifact mechanisms remain R8
install (their      —             not a vibe phase: their `install` publishes into the
 local repo)                      local repo; our machine cache (~/.vibe/cache,
                                  PROP-010) is fed by fetch, not by a build phase
deploy              deploy        phase/profile grammar exists; destination mechanisms remain R8
clean lifecycle     clean         YES — PROP-053, one phase, no pre-/post-
site lifecycle      —             deliberately absent (##NOT-FROM-MAVEN)

3. One extension machine — the synergy law

19Decision. The lifecycle hooks and the compiler plugins are ONE machine with two families of extension points, not two machines. One declaration grammar, one context-envelope shape, one handler taxonomy, one trust gate, one ordering rule. A user who has written a lifecycle contribution knows how to write a compiler transform, and vice versa — this is the mandate's «похоже и синергично» made structural.

3.1 Extension points — one namespace, three families

20An extension point is a string <family>:<name>. Three families: phase: — the default-lifecycle phases and clean (project-scoped moments: phase:build, phase:create, …); compile: — the compiler's two tiers: the §7.2 staged positions (compile:source, compile:document, compile:lane, compile:emitted) and the §7.4 pass tier's single point compile:pass, whose position inside the pipeline is carried by the contribution's pass = { … } declaration; slot: — the per-package materialisation moments that already exist as PROP-020 (slot:pre-install, slot:post-install), re-stated in this vocabulary without changing their semantics, timing, or manifest spelling ([[hooks]](../modules/vibe-workspace/PROP-020-install-hooks.xml#manifest) keeps working verbatim; it is now sugar for two slot: contributions).

3.2 Contributions — the [[extension]] table

21A contribution binds a handler to a point. It is declared as an [[extension]] table — in a package manifest (the package ships and offers the behaviour) or in the project manifest (the host adds its own). The shape is one grammar for every family:

22[[extension]]
id      = "announce"                  # unique within the declaring manifest
point   = "phase:build"               # family:name
handler = { kind = "builtin", name = "log" }
config  = { message = "phase={phase} in {project}" }   # free TOML, passed through

[[extension]]
id      = "xml-squeeze"
point   = "compile:emitted"
handler = { kind = "native", crate_dir = "ext/xml-squeeze" }
auto    = false                       # compile-family default; see ##AUTO-BY-FAMILY
  • 23Fields: id (execution identity — Maven's execution id; also the disable/override key), point, handler (§6), config (free TOML delivered to the handler verbatim — Babel's plugin options), auto (§3.3), and optionally when (a guard: kind = "rust-stack"-style applicability conditions, future work behind this anchor).
  • The same handler may be bound more than once with different ids and configs — Maven's multiple-executions property, verbatim need (e.g. log at phase:validate and again at phase:package).
  • Selectors (the Webpack loader test-rule genre, 2026-08-25 audit): a contribution may carry applies_to = { packages = ["org.x/*"], paths = ["vibevm/vibespecs/prompts/**"] } — declarative include/exclude scoping evaluated by the ENGINE, so a per-document transform (tier-1 compile:source/document) or a per-slot moment runs only where declared, visible to §3.5's registry, instead of every plugin re-implementing filtering privately in config. Absent selector = applies everywhere its point fires, today's behaviour.

3.3 Activation — installation is consent; configuration is not a permission slip

  • 24Installing a package IS the consent to run its extensions (owner ruling 2026-08-25, ##LIFE-MANDATE-NO-CONSENT). No allow-lists, no first-run prompts, no --allow-… flags — the Gradle/Webpack/Clang posture: an ecosystem of native extensions where the trust decision is made once, at dependency selection, like build.rs and postinstall before us. The safety model is not permission dialogs but total observability (§3.5). PROP-020 §2.3 now records this successor at its owning anchors; the old allow-list/first-run/non-interactive permission design remains historical text only.
  • A dependency's phase:/slot: contributions are active once the package is installed — the owner's test scenario verbatim: a plugin declares in ITS OWN manifest and prints. A dependency's compile: contributions default to auto = false — they activate by a host manifest line. The asymmetry is CONFIGURATION, not a permission gate: a compile transform shapes the host's own artifact (which transforms produce MY lane is the host's choice, like spec_format is), while a phase contribution performs the package's own declared job. A package MAY declare auto = true on a compile contribution; it is ignored with a warning — the shape of the host's lane is the host's word.
  • Host activation and override. The consuming manifest activates a dependency's non-auto contribution, or re-configures an auto one, by reference: [[extensions.use]] ref = "org.vibevm.x/y#xml-squeeze" with optional config override; [extensions] disable = ["org.vibevm.x/y#announce"] turns any contribution off. The plural extensions namespace is the host-control surface; singular [[extension]] remains only the declaration table nested in the manifest that provides the contribution. The reference key is <group>/<name>#<id> — stable, printable, greppable.
  • Nothing runs invisibly. Every phase run prints the contributions it will execute (id, point, handler kind, providing package) before running them — the narrator genre the tooling already speaks ([k/n] lines). A ritual whose steps are secret is not a ritual. The full instrument rack is §3.5.

3.4 Ordering — one rule everywhere

25Within one extension point, contributions run in this order: (1) built-in bindings of the effective kind-preset (§4.5) — Maven's «packaging first»; (2) dependency-declared contributions, in lockfile package order (deterministic, no filesystem enumeration), then declaration order within a package; (3) host-manifest contributions and [[extensions.use]] activations, in declaration order. First-to-last, no reversals anywhere — one rule a user can hold in their head, against Babel's plugins-vs-presets special case (##NOT-FROM-BABEL).

3.5 Observability — the safety model is glass, not gates

26The owner's trade (2026-08-25): no consent dialogs, total transparency instead — debuggers, scanners, and internals specialists must be able to see instantly what the extension machine is doing. Observability is therefore a REQUIREMENT of the machine, specified with it, not a later courtesy:

  • 27The extension registry is a query surface: vibe extensions (with --json) lists every declared contribution in the installed world — id, point, handler kind, providing package and version, config, compiler_internals flag, auto/host-activated state, disabled state, and for natives the artifact path + build state + content hash. One command answers "what runs on my project and who brought it". vibe list/show mark internals-bearing packages, as §7.4.3 already requires.
  • Everything a plugin CAN do is declared, so a scanner needs no execution: points, pass positions, formats, the internals flag, prebuilt artifact paths — all in manifests, greppable in the materialised world. A security scanner (the PROP-020 §4 "LLM antivirus" future) reads the whole capability surface statically; the machine adds no runtime-discovered powers.
  • Lifecycle-owned compilation is traceable, LLVM-style: the landed R3.4 surface is direct install, every default phase/chain, update and reinstall; compile sites owned by init, publish staging and uninstall remain explicitly outside this epoch until they gain their own command-owned JTD report boundary. On the landed surface, --trace-compile (or selected-manifest [compile] trace = true) attempts to certify the compiler's generated JTD IR after every successful pass while the run budget and writer permit, the -print-after-all genre, and aggregates every pass outcome/timing, the -time-passes genre. The index records why a snapshot is absent (snapshot-skipped-budget, snapshot-failed, or pass/verifier failure), so observer refusal never becomes compiler failure. One project run lives under .vibe/trace/<run>/: index.json is the generated authority, while certified snapshots use a reversible Windows-safe name carrying the global sequence, encoded pass, scope kind, encoded scope label, encoded artifact id and occurrence ordinal; exact scope/attempt identity remains in the event→scope relation in the index. A name beyond the physical cap uses a digest suffix whose full identity remains in the index. Thus two parse documents, package units and node artifacts cannot collide, and adjacent certified snapshots answer “what did THIS pass change?” without a trace-only IR dialect.
  • One external command owns one current trace and one outcome funnel. Direct install, every default-lifecycle phase/chain, update and reinstall each select one input/identity epoch and one non-Clone current TracePreparation, lend only Option<&TraceRun> below, then consume success|parked|failed exactly once. A displacement may sequentially reopen, finish and drop one existing predecessor before opening the current run; it never creates a second live current owner. The same optional generated CompileTraceReport rides the one registered Install/Lifecycle/Update/Reinstall root; disabled mode omits that optional member exactly and no standalone trace document exists. Reinstall reports the selected invocation node while trace/state/lock remain workspace-rooted; this intentionally corrects the older member-invocation report that printed the workspace root and is an accepted command-identity migration, not a trace-induced byte change. Deferred plan/closure previews may precede the root but never replace or duplicate it.
  • Park/resume and displacement preserve exact ownership. A park suspends a running trace; a flagless same-command resume adopts the exact lifecycle run_id and original start, reopens the existing trace and appends dense sequence/occurrence history. The sticky bit proves a request, not a directory: an adopted run whose trace directory is absent reports unavailable and never creates a partial mid-run history. A displaced identity may terminalise only the state-proven predecessor trace that already exists: durable finalisation emits one bounded structural supersession notice after writer warnings; reopen/finalise refusal emits a bounded refusal without claiming finalisation; absence emits neither notice nor phantom. Slot continuation is likewise owned by the exact persisted run_id; a fresh/displacing identity must not service or re-park another run's debt.
  • Trace is an observer, never a prerequisite. Outside trace-owned .vibe/trace/** diagnostics and the sticky lifecycle request bit, toggling trace preserves command payload artifacts, bytes, mtimes, error identity and exit codes; observable command output differs only by the optional member/presentation. Open/scope/publication refusal becomes bounded observer evidence and ordinary compilation continues untraced; a failed command persists only fixed command failed, never rich script/provider text or secrets, while returning the original typed error object unchanged. Retention keeps the newest nine terminal runs and never collects a running one.
  • The lane analyzer (the bundle-analyzer genre, 2026-08-25 audit): a report attributing the compiled lane's weight — bytes and estimated tokens — per node, per package, per pass-delta (what each transform added or saved), answering «кто сколько приносит в лейн». This is PROP-048's tokenomics law given its instrument: the token budget is a design constraint, so its spend must be measurable at a glance. Surfaced as vibe extensions analyze (or a vibe show lane view) with --json.
  • Every run leaves a record: the phase narration (##SURFACE-THE-RITUAL), the generated artifact's transforms/pass header (§7.1, §7.4.3), and .vibe/lifecycle.toml's per-execution fingerprints, timings and statuses — so "what ran, in what order, on what inputs, how long" is answerable after the fact without re-running.

4. The vibe lifecycle

4.1 Two lifecycles, nine default phases

28vibe has two lifecycles. clean — one phase, exactly PROP-053. default — nine phases, in this fixed order:

29validate → install → generate → build → test → create → verify → package → deploy

30vibe <phase> executes every default-lifecycle phase up to and including the named one, in order — the Maven ritual verbatim. vibe clean <phase> runs the clean lifecycle first, then the default lifecycle up to <phase> (§4.4). A phase with no contributions and no built-in binding completes as a no-op — Maven's empty-phase law, which is what makes nine slots cost nothing for a prompt-only project (its deploy chain degenerates to validate+install).

31The engine, as an algorithm (normative; written out so no implementer has to reverse-engineer it from prose):

32run(requested_phase, pkgrefs, flags):
  1  root      = resolve_project_root(flags.path)          # refuse outside a vibe project
  2  manifest  = parse vibe.toml; world = read vibe.lock   # both may be absent for validate
  3  chain     = [validate .. requested_phase]             # §4.1 order; clean prepended if asked
  4  contribs  = collect():                                #   for EVERY phase in chain
       a. kind-preset built-ins (§4.5)                     #   order rule §3.4: (a) then (b) then (c)
       b. [[extension]] of installed packages, lock order
       c. host [[extension]] + [[extensions.use]], decl order
       drop disabled; apply selectors (##CONTRIB-SELECTOR)
  5  print the ritual: every (phase, execution) to run     # ##SURFACE-THE-RITUAL
  6  for phase in chain:
       state = read .vibe/lifecycle.toml
       for exec in contribs[phase]:
         fp = fingerprint(exec)                            # §4.3: inputs hash
         if !flags.force and state[exec].fingerprint == fp:
             report "fresh"; continue
         reply = dispatch(exec, envelope(...))             # §5, §6 by handler kind
         record fingerprint, timing, status, artifacts
         if reply.status == fail: stop per ##FAILURE-BY-PHASE
         if engine outcome == delegated: park + exit       # §6.5; reply status stays ok|fail|skip
  7  write .vibe/lifecycle.toml; exit 0

4.2 What each phase means

  • 33validate — the cheap preflight: the manifest parses, the role/epoch/format are supported, declared extension references resolve to installed packages. Seconds, no network, no LLM. It is NOT vibe check (which stays a standalone diagnostic verb, our mvn site-grade tool); the phase is the subset whose failure makes every later phase meaningless.
  • install — the whole existing PROP-009/PROP-011 machine: resolve, fetch, materialise vibevm/vibedeps/, regenerate the boot lane (which is where the compile: family fires, §7.1), write the lock. Strictly incremental by the owner's law: manifest unchanged + lock satisfied + sources fresh ⇒ the phase reports fresh and does nothing (##EMPTY-REQUIRES-IS-A-NO-OP and the PROP-011 freshness gate already carry most of this; §9 closes the remaining hole — the per-file diff). Runs FIRST among real phases because everything downstream reads the world it materialises.
  • generate — derived sources from specs/prompts (the spec-driven codegen genre the host already practises as cargo xtask codegen): K6.5-table→constants, schema→types, prompt-catalog→registry. Empty by default; stack presets and plugins populate it. Sits between install (needs the world) and build (produces what build compiles).
  • build — the deterministic algorithmic build. The engine prepares selected native groups, replays pending compiler lanes, executes dependency-ordered artifact targets through exact mechanisms, then phase rows. Builtin Cargo, compatibility [[binary]] lowering and package-supplied native Build replacement are commissioned; the foreign adapter consumes exact prepared carriage, owns staged output proof/A2 records and preserves provider freshness through rollback.
  • test — deterministic checks: stack preset binds the language's test runner; discipline packages contribute their conform/oracle/tcg gates as plugin contributions (this is exactly the owner's «на вызов фазы типа vibe test должно быть возможно повесить алгоритмический хук или вызов агента»). No LLM by default.
  • create — the optional agentic producer, our phase Maven does not have. Long, non-deterministic and potentially expensive — therefore LATE (after test: the deterministic baseline is proven before tokens are spent) but before verify/package/deploy. Contributions may use handler = { kind = "agent" }: from a terminal the configured vibe-llm provider executes each explicitly activated contribution as one bounded execution; under an agent host the §6.5 handshake delegates it. An agent handler is a declared workload in its own right, not an LLM enhancement of some hidden algorithmic twin: once activated it fails honestly when neither a provider nor an agent host can execute it, rather than silently disappearing. Provider/config/credential presence activates nothing; omission, host disable or freshness keeps the ordinary chain algorithmic and spends nothing. The phase itself is not a coding agent, planner or repair loop: it executes declared contributions, records/parks their outputs and returns. Skip-when-fresh is vital so an unchanged prompt/spec fingerprint never re-spends tokens (§4.3).
  • verify — Vibe's adaptation of Maven's late quality-gate role, intentionally placed over the current build+create outputs before Vibe package. The engine first reconciles the exact declared input-set and artifact witnesses produced by earlier evidence-bearing work; then configured checksums, contracts, build/test/conform evidence and optional judge contributions run. A prior measurement is matched only for the exact run/selected-node/input/artifact identity §4.6 defines; mismatch is reported as a typed stale/missing/unstable observation and stops before package, never as the heuristic word unmet. The engine neither edits source nor inserts a back-edge to create. Trusted verify handlers retain the same extension powers as handlers elsewhere, so evidence-only behaviour is a contribution contract, not a sandbox Vibe does not have. A project may declare no evidence-bearing or verify contributions; that yields typed unavailable evidence rather than an invented pass. An external human or orchestrator reads the generated outcome and decides what happens next (§4.6).
  • package — assemble distributables without mutating destinations. Verified artifact records feed builtin static-skill, Agent Plugin/client projection and deterministic Windows-zip providers, plus package-supplied native Package replacement. The foreign adapter resolves and revalidates the real Build A2 record before invocation and retains engine-owned fingerprints, proof and records.
  • deploy — apply selected package artifacts to explicit destinations through profiles, exact providers, plan/lock/intent/receipt/recovery/inverse ownership. Builtin local/client providers and a real package-supplied native replacement are commissioned; live remote publication remains subject to ordinary red lines.

4.3 The incremental law — every phase is skip-when-fresh

  • 34Every phase execution owns a fingerprint — a hash over its declared inputs (for install: manifest+sources, already built; for build: the slot sources + project sources the preset names; for create: the prompt documents + their spec closure). A phase whose fingerprint matches the recorded one skips, reporting fresh per contribution — the Gradle up-to-date property grafted onto the Maven ritual. This is the design answer to «если vibe.toml не поменялся, инсталлировать пакеты не нужно», generalised to all nine phases because create (tokens) and build (native compile time) are even more expensive than install.
  • Fingerprints and last-run records live in .vibe/lifecycle.toml at the workspace root — machine state beside the project settings that already live in .vibe/, inside the shippable-tree denylist (PROP-024 §2.2), never committed, never hashed. vibe clean does NOT remove it (it describes work, it is not derived prompt state); a --force flag on any phase ignores it for one run.
  • Freshness is judged per contribution, not only per phase: one stale execution re-runs alone; its peers stay skipped. A handler MAY declare extra inputs in its manifest entry (inputs = ["spec/prompts/**"]); a handler that declares none is conservatively re-run whenever the phase's own inputs move.

4.4 The command grammar — PROP-053's chain, generalised by its own rule

35vibe [clean] <phase> [<pkgref>…] [flags]. Any default-lifecycle phase may be named; clean before it runs the clean lifecycle first. vibe clean install org.x/y --offline keeps its exact PROP-053 semantics (wipe → world from lock → refresh the named) as the special case it already was. This section IS the «own semantics ruling» PROP-053 ##CHAIN-ONLY-INSTALL required before any wider grammar — that anchor is superseded by this one at build time, by a status move and a successor note beside it, never by deleting or renaming it. Pkgrefs remain meaningful only to the install phase (they scope what refreshes); other phases take flags, not pkgrefs, until a ruling says otherwise.

36Existing verbs are untouched: vibe check, vibe bin build, vibe skill, vibe cache … keep their meanings as directly-invoked tools — Maven's «a goal not bound to any build phase could be executed outside of the build lifecycle by direct invocation». vibe install the verb IS the install phase invoked directly (one implementation, two spellings of the same thing). Mixing direct verbs into a phase chain on one command line (Maven's mvn clean dependency:copy-dependencies package) is not adopted now — each verb keeps its own invocation; revisit behind this anchor if the need shows.

4.5 Kind-presets — our packaging element

  • 37The manifest's existing kind (flow, feat, stack, tool, mcp, lang) plus the installed stack select a binding preset — the table of built-in contributions, exactly Maven's packaging→bindings table. A prompt-only flow package binds nothing beyond validate/install (the pom-packaging analogue); a project with the rust stack installed gets generate/build/test bound to the cargo genre; a skill-bearing package gets a package-phase binding. Presets are DATA (a table shipped in vibe or contributed by a stack package), not code branches.
  • A stack package may ship its preset as a set of [[extension]] contributions in its own manifest (auto, trust-gated like everything else) — this is how rust-ai-native-lang teaches vibe build/vibe test to drive cargo without vibe hardcoding cargo, the OOS-AUTODETECT posture of PROP-024 kept intact: the package declares, vibe never infers.

4.6 The lifecycle is a framework; the development loop is external

38Owner ruling (2026-08-27): VibeVM builds the mechanics, not a universal coding agent. The lifecycle is a passive, extensible execution framework: phases, contributions, fingerprints, artifacts, reports, durable park/resume and CLI/MCP adapters. It contains no built-in Plan/Act policy, no automatic create → verify → create transition, no replanning heuristic, no hidden attempt/token loop and no rule for when a human must intervene. Any human, Codex, Claude Code, OpenCode or future agent may orchestrate repeated invocations; a reference coding agent is explicitly a separate future campaign.

39External orchestration is resumable and evidence-driven. An orchestrator may version intent/specs, invoke one bounded create/verify command through the ordinary CLI or hosted lifecycle_run, inspect generated evidence and the optional R7.5 requirements query, then choose to accept, edit, replan, ask a human, invoke another phase or stop. A direct vibe verify still obeys ##INVOKE-RUNS-PRIORS and therefore runs/skips every prior phase, including create; it is not a shortcut around the lifecycle. The same law applies to hosted resume: re-invoking the parked phase re-enters its inclusive chain, so a predecessor invalidated by host-written output reruns and remeasures before the delegated row is accepted — this is linear recomputation, not an automatic repair loop. Every invocation re-reads the actual tree/state; human edits between invocations are ordinary inputs, never corruption of an imaginary uninterrupted agent process. Every query is physically read-only, creates no lifecycle state/lock/outbox, constructs no provider and spends no tokens. Package/deploy run the same linear prior-phase chain and stop on verify failure; they do not trigger an autonomous repair loop.

40Verification evidence is valid only for the exact identity it measured. The identity is the tuple of the producing run id, selected workspace node, qualified execution reference, declaration fingerprint, declared input-set witness and consumed/produced artifact witnesses. Each witness names its digest algorithm and exact scope. A measured_run_id and its measured witness are one attributable measurement and are therefore present exactly together: neither an unowned witness nor a run id pointing at a measurement the row says does not exist is evidence; the outcome matrix separately permits only unavailable to omit the pair. An ordering fact such as “build ran earlier in this chain” proves no identity equality. Evidence for any other tuple is stale, missing, unstable or unavailable — never a pass and never the synthesized status unmet. Verify reports the comparison and stops on a real mismatch. An external orchestrator may invoke verify again: incremental generate/build/test recompute against current inputs, create skips when fresh, and verify can accept matching witnesses — two durable invocations, no hidden engine back-edge or unbounded loop.

41The declaration fingerprint binds the effective executable declaration under its own epoch. SHA-256 starts with vibe-execution-declaration-v1\0epoch=1\0 and uses the common length frame. In order it binds qualified execution, phase and point; optional portable slot coordinate (group/kind/name/version, never absolute root); exhaustive handler kind/payload with explicit native option/map presence; canonical effective config; provider kind (dependency|host), id, version and content-hash presence; absent-versus-present declaration-order input patterns; semantic pass fields and compiler_internals with explicit option/list presence; and optional resolved agent prompt address plus exact bytes. Presence is ASCII 0|1, counts are canonical decimal, paths use forward slashes, enums use closed lowercase wire spellings, maps are deterministically ordered, and the result is sha256: plus 64 lowercase hex. Requested chain/mode, machine/project/world paths, manifest/lock, accumulated artifacts and current input bytes are separately owned identities and do not enter. Authored config replaced by an effective override, auto, selectors/when, controls, tiers and ordinals decide plan membership/order rather than the work delivered and are excluded. Code exhaustively classifies every declaration field and handler variant so a future grammar member forces an epoch ruling at compile time.

42A tree witness certifies a declared byte set, not an imaginary atomic filesystem snapshot. Evidence-bearing work names project-relative input patterns. Vibe hashes a no-follow, single-link, canonical manifest of the regular files those patterns select (path + bytes, deterministic order, domain-separated algorithm) and records pattern/file/byte counts; absent declaration is typed unavailable. The one input walk preserves the old execution fingerprint's pattern-major repeated stream, while evidence uses the deduplicated file union. The evidence algorithm domain is sha256:vibe-input-manifest-v1\0epoch=1\0; it length-frames declaration-order pattern count/patterns, sorted union file count and each path/size/bytes, then total bytes, all counts canonical decimal. Authored empty inputs produce the real zero-count digest; absent inputs produce no measurement. A file is accepted only when handle identity/length stay equal around two identical bounded reads; any observed disagreement yields unstable. This is detection-bound evidence, never a claim that an adversarial writer could not have written the same bytes between observations, and files are not one atomic tree epoch. The aggregate tree witness is a digest over these per-execution manifests. It never scans or hashes arbitrary build caches, dependency stores, target/, node_modules/ or the whole machine merely to mint identity. A detected source change yields unstable, not a hybrid read blessed as exact.

43Evidence observation does not rewrite the legacy execution decision and its state carriage is closed. One tree enumeration yields one logical union row per selected regular path; accepted evidence uses two identical capability-relative bounded reads around equal no-follow single-link proof and length, so “one raw read” is not a product claim. An evidence-only refusal on a legacy-regular path falls back to the ordinary raw bytes for the unchanged execution fingerprint but emits no input measurement; a selected link/reparse/non-regular entry remains unread and ignored by that legacy projection while refusing the whole evidence manifest. No partial manifest is stored and evidence refusal alone does not veto the handler. Current ordinary ok|skip, fresh skip and hosted satisfied resume checkpoint the current invocation's measurement and run id in the same record transaction; dispatch/preparation/fingerprint failure, hosted park and state-blind clean carry none. Fresh remeasures and never copies the prior witness; refused fresh observation drops the old claim. Measurement is prepared before dispatch and is never overwritten by post-handler input bytes.

44Artifacts carry independent content identity. A regular file is witnessed by its bytes; a directory artifact by a canonical, link-free relative-path tree digest. Durable lifecycle state may retain the existing absolute machine path needed to reopen it, but the external evidence row records the canonical project-relative path under run.selected; selected node + relative path is the exact portable identity and does not leak the operator's home. The durable witness records id, kind, path, algorithm and digest when produced or accepted by a hosted resume; verify recomputes it. A fresh skip preserves that prior witness/run baseline and keeps its current re-probe transient until verify — it never replaces W1 by W2 or upgrades legacy absence, because either would make external output mutation match itself. Missing, physically aliased, changed or unwitnessed legacy output is named separately; output existence alone never proves freshness. Hashing an explicitly declared artifact is verification work, not permission to hash unrelated project or system trees.

45Artifact evidence follows the invocation's complete accumulated registry, not only the phase-prefix rows. Install-stage slot outputs are included even though no RitualPlan phase row names them. Each successful ordinary/fresh/hosted checkpoint remembers its exact artifact rows in the invocation only after state publication succeeds, so verify pairs every current accumulated id with its actual durable baseline instead of scanning stale state history for an owner; park, failure and untracked paths remember none. Input evidence remains the completed phase-plan prefix and does not acquire slot inputs by analogy.

46Artifact witnesses stream without a byte cap and preserve empty directory structure. Safefs reads a regular single-link file twice in fixed chunks through the same held no-follow handle, requires equal raw SHA-256 and byte counts, and proves the final name still denotes that object. sha256:file-v1 length-frames canonical-decimal size plus raw 32-byte content_sha256. sha256:tree-v1 walks artifact-root-relative entries in deterministic preorder (each directory's direct UTF-8 names byte-sorted), framing entry_kind, forward-slashed path, and file size/raw content_sha256, then final directory_count, file_count, total_bytes; the root is excluded and every descendant directory including an empty one is included. The tree has no total entry/byte cap, applies no project-input exclusion, and refuses a direct directory wider than 1,000,000 names or depth above 256. Artifact witness files/bytes members remain absent — counts are digest material. Refusal is per artifact and whole-tree within one directory artifact. Fresh re-probes; witness fields never participate in agent output identity comparison.

47The closed evidence-comparison vocabulary is matched | stale | missing | unavailable | unstable. matched requires every present required witness to compare equal. stale is a digest/declaration mismatch. missing is reserved for strict absence of an owed path/row. unavailable is an honestly undeclared or legacy witness with no prior measurement. With a prior measurement, unstable means the current observation could not establish one safe comparable object: movement/torn reads, non-regular/link/reparse/hardlink, portable alias, escape/malformed path, bounded-directory refusal or I/O all use that word with a cause-specific closed reason. The generated lifecycle root keeps this comparison separate from the command's own ok and handler outcomes: no evidence rows is visible unavailable, while stale/missing/unstable evidence stops verify before package. A later verify-handler failure may coexist with a matched identity and remains a handler failure, not a rewritten evidence status.

48JTD owns the evidence wire once. One generated verification-evidence member is produced by the lifecycle library and attached to the existing generated LifecycleReport whenever verify actually reconciles evidence. CLI vibe verify --json and MCP lifecycle_run({phase:"verify"}) return that same member; terminal text is a projection. Per-execution input measurements and artifact witnesses live in generated lifecycle state so a park/resume and a second invocation compare the same durable claims. evidence_id is SHA-256 under the domain vibe-verification-evidence-id\0epoch=1\0 over every member except itself and observed_at, using the existing length-framed field primitive in schema order, canonical-decimal numbers/counts, wire enum spellings and explicit optional-presence bits — never Rust layout or JSON key/pretty-print order. Neither surface rebuilds, reinterprets or conditionally reshapes the member.

49The evidence-id labels are part of the epoch, not implementation trivia. After the domain seed the writer frames, in order: evidence, status; run.run_id, run.selected, run.requested, run.chain.count, each run.chain.item, run.started; inputs.count, then for each row inputs.execution, inputs.phase, inputs.declaration_fingerprint, inputs.patterns.count, each inputs.patterns.item, inputs.status; artifacts.count, then for each row artifacts.id, artifacts.kind, artifacts.path, artifacts.status. Each row then frames optionals in vocabulary order measured_run_id, measured, observed, reason_code: first <path>.present = 0|1, then the value when present. A witness value frames <path>.algorithm, <path>.digest, then optional files and bytes, each with its own .present bit and canonical-decimal value. The repeated path-qualified labels plus array counts are the canonical row framing; evidence_id and observed_at have no frame. A longhand golden independent of the production writer pins this schedule, including vocabulary order files before bytes.

50Verify reconciles the completed current-plan prefix, even when verify has no user contribution. The boundary is before the first verify-or-later execution; only current validate through create executions that completed before it may contribute input measurements/artifacts. Verify rows and future package/deploy rows have not produced evidence and are excluded. If the chain contains verify but no execution marks that phase, the engine still reconciles after the prefix and before any later suffix, so an empty verify phase cannot bypass the gate. An external input row carries the current reconstructed declaration fingerprint/patterns; the durable fingerprint is compared to it. Equality is required for matched, difference is stale (input-declaration-changed) and the current spelling enters evidence_id, while the measured witness/run remain the explicitly prior half of that stale comparison.

51Only the complete default-phase epoch may arm verify. The post-durability install callback executes a partial validate/install plan while carrying the outer command's full chain, which may already name verify/package/deploy; chain text alone therefore cannot authorize reconciliation. The complete phase dispatcher passes Some(observed_at) as both explicit boundary permission and a surface-injected instant, while every partial/state-blind epoch passes None; lifecycle/orchestrator reads no clock. At the armed boundary matched|unavailable attaches the member and continues, while stale|missing|unstable produces an ordinary measured command failure (ok:false, non-zero CLI exit / executed-failure MCP result) before the suffix. The failure carrier preserves the exact generated member and observer emission policy; a later verify-handler or checkpoint failure may carry a still-matched member. Report builders attach and surfaces project this value, never recompute it.

52Requirements metadata is an optional read-only observation source for external agents. Identity is always the full spec://<group>/<package>/<path>#<fact> address. Status-bearing sources are exactly vibe-specdoc authoring markers and the consumer-owned vibe-facts adoption overlay. Compiler IR and specmap do not author status. Current project specmap data and carried package maps may enrich rows with typed relations; their absence or inability to prove freshness is typed enrichment loss and never changes lifecycle execution or evidence. A current host map is built once in memory only when its configured namespace equals the selected host coordinate. A carried package map is never trusted by presence/mtime or rebuilt from possibly unshipped transformed source: the provider receives the lock-selected content hash/root, requires the slot record's source hash to match, requires that record to own package.specmap.json, then performs one capability-relative no-follow/single-link read whose exact bytes are hashed against the row and parsed without reopening; absent record/map is unavailable, either hash mismatch is stale and matching malformed JSON is invalid. This proves the exact published carried-map byte. Relation-provider outcomes never choose wire provenance — the query library derives it from source kind — and all emitted edge files are workspace-root-relative (selected-node prefix for host, slot prefix for package).

53Four questions stay four typed observations. Authoring status says what the source document claims; consumer adoption says what this project recorded (or that no record exists); relation-provider state says not-requested | current | carried | stale | unavailable | invalid plus provenance; verification evidence says what one exact run/input/artifact identity measured. No field, enum value, rollup or convenience boolean is named unmet, fulfilled or verified: combining these axes into a next-work verdict is the external orchestrator's policy, never a lifecycle or query decision.

54An authored terminal-artifact contract is part of authoring, not a fifth verdict axis. Epoch 2 of the requirements report carries the optional canonical requires set from PROP-043 beside authoring status. Absent means unclassified and present is non-empty, sorted, unique and closed to the artifact vocabulary. The metadata query does not emit terminal, fulfilled or another synthetic join: external consumers may combine the declaration with adoption, relation and lifecycle observations under their own policy, while the progress report may name its local provider-bound terminal observation explicitly. Epoch 1 readers remain strict and reject the new shape rather than silently misreading it.

55The R7.5 requirements query returns bounded metadata only: observation/source digests, selected-node and optional current lifecycle run identity, full fact address, source coordinate/kind, optional authoring status and terminal-artifact requirements, separately-present consumer adoption, and optionally requested relation edges/provenance. Base source identity is the package coordinate with exactly one host|package kind value — the same coordinate may not occur under both kinds — because relation-source enrichment keys by coordinate alone and must recover one unambiguous kind to validate fresh-versus-carried provenance; every relation-source result therefore binds to exactly one enumerated base source. Source readers parse the same one-read raw bytes they witness and expose only {path,bytes,sha256} below the query, never bodies. Per-source digest keeps the domain vibe-requirements-source-digest\0epoch=1\0 over kind/package and sorted raw document witnesses; source_digest keeps vibe-requirements-scope-digest\0epoch=1\0 over selected, sorted source digests and registry witnesses; epoch-2 observation_id uses vibe-requirements-observation-id\0epoch=2\0 over every report member except itself and observed_at, including optional lifecycle run id and the authored requirement presence/count/kinds in schema order. All three use the same length frames/canonical decimals/explicit optional bits as evidence identity, never JSON or Rust layout. It returns no fact prose, code body, prompt, recommendation, terminal verdict, ranking or next task. It reads authored specs/materialised package documents and the existing adoption registry without sync or write; relation enrichment is injected once per requested query, while unrequested enrichment emits explicit not-requested/none per source and calls nothing. Provider availability is mapped per source and the library derives current/carried provenance from source kind. Missing registry/map/package participation produces a generated partial/unavailable value; a malformed present authored source is named in its own source result and never becomes lifecycle failure, while malformed registry or lock aborts because the query could not establish its scope.

56The epoch-2 row insertion is exact. Each row keeps the epoch-1 order address, kind, package, authoring, authoring.status.present, then stage and state when status is present. Immediately after those fields it frames authoring.requires.present = 0|1; when present it next frames authoring.requires.count as canonical decimal and one authoring.requires.item per kind in PROP-043 canonical vocabulary order. Adoption presence/status and every later row/report frame then continue in their epoch-1 order unchanged. Labels and position are epoch material. An independent longhand golden that does not call the production writer pins absent, singleton and multi-kind vectors.

57Non-normative reference scenario — PDSA, not an engine state machine. Plan = human/agent intent and specs; Do = one bounded create/edit attempt; Study = verify plus whatever deterministic tools the orchestrator selects; Act = the external decision to accept, revise, replan, ask or abandon. Non-normative is structural: none of these four names may become a phase, enum variant, state-machine member, wire field, CLI verb or automatic transition. R7.5 proves composition with a fake external process that invokes only normal lifecycle control and read-only requirements metadata through two adjacent oracles: hosted park/resume may match after the inclusive chain reruns invalidated deterministic predecessors, while a local create that changes a previously measured input later in one uninterrupted invocation produces stale and only an external second invocation recomputes it. This demonstrates a common AI-assisted workflow without privileging it over TDD, issue→PR, pair programming, research spikes or another plugin-defined process.

5. The context envelope — what a contribution receives

58Every handler, in every family, receives one context envelope — a versioned JSON document (the wire form for script/binary/native handlers; builtin handlers get the same data as a Rust struct; agent handlers get its prose projection). Modelled on what Maven hands a Mojo — MavenProject, MavenSession, MojoExecution, per-parameter configuration — translated to our world and extended with what the owner predicted: «в стадии будет попадать много больше, чем их просто название».

59{
  "envelope": 1,
  "point": "phase:build",
  "execution": { "id": "announce", "package": "org.vibevm.x/y", "config": { } },
  "project": { "root": "…", "name": "…", "version": "…", "kind": "…",
               "manifest": "vibe.toml", "spec_roots": ["vibevm/vibespecs"] },
  "world": { "lockfile": "vibe.lock", "deps_root": "vibevm/vibedeps",
             "packages": [ { "group": "…", "name": "…", "version": "…",
                             "kind": "…", "slot": "vibevm/vibedeps/…" } ] },
  "run": { "requested": "package", "chain": ["validate","install","…"],
           "phase": "build", "offline": false, "assume_yes": false,
           "agent_mode": "cli", "force": false },
  "artifacts": [ { "id": "…", "phase": "build", "path": "…", "kind": "…" } ],
  "io": { "scratch": ".vibe/lifecycle/<run>/<execution>/" }
}
  • 60The Maven correspondence, so the translation is checkable: project ↔ MavenProject (coordinates, dirs, model); world ↔ the resolved dependency set + session repository view; run ↔ MavenSession (request, properties, offline flag); execution ↔ MojoExecution (id + configuration); artifacts ↔ attached artifacts; io.scratch ↔ the plugin's work directory convention.
  • The artifact registry is how phases talk. A contribution's reply may declare artifacts ({"artifacts":[{"id","path","kind"}]}); later phases receive the accumulated list (create's outputs are what package packs). The registry is part of the run, persisted into .vibe/lifecycle.toml for freshness accounting — never a hidden channel.
  • The reply envelope (same versioning): {"status": "ok"|"fail"|"skip", "message": "…", "artifacts": […], "tasks": […]}tasks is non-empty only from agent handlers under the §6.5 handshake. script handlers reply by exit code + a VIBE_REPLY file in scratch (streams stay human); binary/native reply with the JSON itself.
  • The compile: family rides the SAME envelope with a payload member instead of artifacts/run-chain: {"payload": {"stage": "emitted", "format": "xml", "bytes_b64"| "text": …, "doc": …}}, reply {"status", "payload"} — one shape, two families, which is the synergy law §3 made mechanical.
  • Failure semantics generalise PROP-020 §2.5: a failing contribution in validate/install/generate/build/test stops the chain (the phase reports which execution failed); a failing create/verify contribution stops before package (nothing half-created is packaged); package/deploy failures stop the chain trivially (they are last). slot:pre-install keeps its rollback; slot:post-install keeps installed-but-flagged. A skip reply is bookkept, never an error.

6. The handler taxonomy — five kinds, two of them already ours

61A contribution's handler.kind is one of five. Two wrap machinery that exists; three are new. All five are legal at every phase:/slot: point; the compile: family accepts builtin and native only (§8.5 explains why).

  • 62builtin — a named handler compiled into vibe (log, the §10.1 test bearer; future: copy, zip, checksum). Zero trust surface, zero build cost; the vocabulary grows only by shipping vibe.
  • script — a base path resolved .sh/.ps1 with the PROP-020 §2.2 interpreter ladder, environment (VIBE_*) and streams, unchanged — plus the envelope on VIBE_CONTEXT (a file path in scratch, not an env blob). The existing [hooks] table compiles to two script contributions at slot: points; nothing published breaks.
  • binary — a [[binary]] name from an installed package (PROP-025): built if missing (PROP-025's build flow; its build-consent prompt is superseded by ##INSTALL-IS-CONSENT at build time — narrated, never asked), then invoked with the envelope on stdin, reply on stdout. The right kind for per-run tools where process-spawn cost is irrelevant.
  • native — an admitted ABI-1 cdylib loaded in-process from an exact current-platform prebuilt or provider-root source artifact. Lifecycle and compiler handlers plus deploy/build/package mechanism providers reuse one quarantined loader/image/cache/free law. Each ABI family retains its generated request/reply contract and one image declares only that family.
  • agent — a prompt-bearing execution: handler = { kind = "agent", prompt = "spec://…" }, the prompt being a spec document the package ships. Executed by §6.4/§6.5 depending on run.agent_mode. The only handler kind that may spend tokens, and the reason create is late in the line.

6.4 Agent handlers from a terminal — vibe-llm

63In agent_mode = "cli" (a human at a terminal, no agent host detected), an activated agent execution runs through the real vibe-llm seam: resolve the prompt address and recursive embed closure against the exact selected package world, assemble the credential-free envelope prose, call the configured provider once, validate the strict generated result and publish every declared output through the shared safe-filesystem transaction. No provider/configured credential for this explicitly activated workload fails with remediation («configure [llm], disable the contribution, or run under an agent»), never a silent skip. This is not a contradiction of ##LLM-ENHANCEMENT-MODES: a pure agent handler has no algorithmic twin and is the workload the author explicitly selected; enhancement modes apply only where a complete algorithmic subsystem already exists.

64The first provider id is exactly openai-compatible: synchronous object-safe LLMProvider::chat, generated epoch-1 Chat request/response, blocking bounded transport and provider-independent usage. User config owns provider/model/endpoint/token-file; selected project [llm] owns default provider/model and an optional credential env source. Project provider/model win independently; endpoint remains operator-owned. A nonempty project env source wins over a token file and fails honestly when absent. Keyed traffic requires HTTPS; keyless HTTP is literal loopback only; redirects are disabled, loopback bypasses ambient proxies, response bodies/timeouts are bounded, and keys/query/body/raw provider responses never enter diagnostics.

65CLI execution is lazy by construction: provider/config/credential resolution happens only after one non-fresh activated agent row reaches dispatch. No selected row, a disabled row, a fresh row, hosted mode and dry/read-only queries construct no provider and read no credential. Multi-output apply is planned completely before its first mutation and validates the exact output contract after publication.

6.5 Agent handlers under an agent — the delegation handshake

66In agent_mode = "agent" (detected via env — the CLAUDECODE/VIBE_AGENT genre — or forced by --agent-mode), vibe does NOT call an LLM. It parks the work and hands it to the hosting agent: for each agent execution it writes a task file under .vibe/agentic/outbox/<run>/<execution>.md — the resolved prompt, the envelope's prose projection, and the output contract (paths + acceptance) — then prints a fenced vibe-agent-tasks block naming the run id and every task file, and exits the phase with engine status delegated (the handler reply vocabulary remains ok|fail|skip; chain pauses and nothing downstream runs). The hosting agent performs the tasks, writes the outputs, and re-invokes the same phase: vibe finds the outputs, validates them against the contract, marks satisfied executions done and continues — exact-run resumability, no special resume verb. The parking place and relay posture are PROP-018's agentic-relay genre extended from one command to a phase's worth of tasks. An MCP surface (lifecycle_run/lifecycle_tasks) is a second adapter over the same state/outbox, not a second mechanism and not an autonomous development loop (§4.6).

67R7.4 exposes the same command and mailbox over MCP: strict lifecycle_run accepts exactly one default phase, runs under the outer workspace lease with the real shared package source, capture-only child streams and the credential-free hosted backend, and returns the same generated lifecycle report as CLI JSON. A park is successful generated content with a durable delegation; handler/install failure is isError:true while retaining the generated executed-prefix root. lifecycle_tasks is a bounded, optimistic, final-name-safe read of the same state/outbox. Neither tool accepts a provider/model/path/force/clean override or links a paying backend.

6.6 LLM enhancement policy — advanced, explicit, never the floor

68Every core VibeVM subsystem retains a complete algorithmic mode. An LLM may improve quality when the operator explicitly enables that feature; credentials, endpoint presence, provider configuration or construction never activate a feature. VibeVM remains useful with no provider, no API access and no agent host.

69Each genuinely LLM-enhanceable feature declares off | assist | required; an undeclared enhancement mode is off. off runs only the algorithmic implementation. assist may call the configured provider and falls back to the algorithmic result with a visible degradation record on unavailable/failure. required is an explicit operator choice and fails with remediation when the paid enhancement cannot run. The existence of required never permits removal of the subsystem's algorithmic mode. A pure handler = { kind = "agent" } contribution is a separately declared agent workload, not an enhancement mode: once activated, its absence would be a silent skip and ##AGENT-CLI/##AGENT-HANDSHAKE govern it.

70Construct/read a provider only after the selected feature is non-off, non-fresh and actually reaches its LLM step. Algorithmic runs, fresh runs, plans/queries, hosted outbox delegation and features not selected read no token and call no API.

71Each enabled enhancement or explicit agent workload carries per-run call, input-token and output-token ceilings. Narration names feature/contribution, mode, provider/model, reason, cache posture and ceilings before spend without exposing secrets. Completed state records provider-reported usage. Exceeding a ceiling follows the selected contract: assist falls back; required or an explicit agent workload fails. Exact default ceilings and manifest grammar land with the first enhancement that consumes them; no global create budget exists.

7. The compiler extension points — two tiers: staged transforms and full passes

7.1 Where the compiler runs, and whose word activates a transform

72The spec compiler (PROP-035 §8) runs inside boot-lane regeneration — today a step of install — with the fixed internal pipeline: parse/topo of the #use graph → #source merge → #embed expansion → per-node qualification + absorbed-node filtering + cross-node short-link resolution → marker-wrapped emit. Those internal passes ARE visitors in the Babel sense; they stay internal and ordered by the spec, not pluggable — determinism of the boot lane is a PROP-009 constitutional property.

73Transforms are activated by the manifest of the workspace whose lane is being compiled (host manifest for the host lane, package manifest for a package's own lane) — ##AUTO-BY-FAMILY's host-opt-in rule. The activated list, in §3.4 order, is recorded in the generated artifact's header comment (<!-- vibe:transforms … -->) so a reader of STATIC.xml can see what touched it — the same honesty rule as ##SURFACE-THE-RITUAL.

7.2 Tier 1 — the four staged transforms

  • 74compile:source — one document's raw text before parsing (the preprocessor seam; Babel's parse-options analogue). Payload: text + its spec address. May rewrite text; may not change the address.
  • compile:document — one document's parsed tree (the specdoc/DocTree JSON projection) after parse, before it enters the closure. The transform-plugin analogue at document grain: rewrite prose, drop or annotate nodes, normalise vocabulary. One call per document, whole-tree payload.
  • compile:lane — the assembled lane as an ordered node list (origin, slug, body per node) after the internal pipeline, before serialisation. Where cross-cutting compression genres (the future Caveman pass) live: they see everything that will ship, in final order, still structured.
  • compile:emitted — the serialised artifact bytes (per artifact: STATIC.xml, STATIC.md, INLINE.*) as the last word before the file is written. Byte-to-byte transforms; the §10.2 minifier's home.
  • Within tier 1 there is deliberately NO closure/graph access — a staged transform cannot add or remove #use edges or change which nodes enter the lane. What loads stays decided by authored directives, auditable from sources alone; a transform that could silently widen the closure would be a prompt-injection lever. Full IR access — closure included — EXISTS, but only in tier 2, behind the compiler_internals flag, host activation, and the header record (§7.4.3): the capability is not denied, it is priced.

7.3 Transforms versus the oracles

75The byte-stability oracles (convert-source round-trips, check-codegen-style diffs) judge the UNtransformed compile — transforms apply after the oracle's reference output is taken, and the recorded transform list (§7.1) is part of the artifact's identity (a changed transform set ⇒ the lane is stale, regenerate). Otherwise every transform would read as corruption to the very instruments that keep the compiler honest.

76Every generated boot artifact set publishes as one crash-recoverable unit. Node and per-unit paths render the complete INDEX and compile the optional selected STATIC before touching the existing artifact set, then one transaction applies INDEX bytes, selected STATIC presence/bytes and stale STATIC absence; node-only redirect selectors join that same transaction, while package units create none. A render/compiler/backend refusal leaves every prior byte exact. Interruption rolls forward or back from durable intent to one consistent set. A fingerprint-fresh unit returns before compilation/publication, preserving bytes and mtimes; a byte-equal dirty recomputation is a transactional no-op at each unchanged target.

77One owner-scoped TransformPlan is a typed, semantic, canonically digested compiler value. Workspace lowers effective registry rows into a vibe-spec plan containing typed provider components, dense effective order, stage, implementation identity, exact effective config and the compiled selector — no manifest object, resolver, display-string parser, Rust layout or trait object crosses the boundary. Config is a lossless TOML semantic tree: sorted tables, ordered arrays, i64 integers, canonical f64/NaN bits and field-preserving datetime; absent differs from authored empty. SHA-256 uses the compiler's length-framed LE primitive under separate epoch-1 domains for config, implementation and plan, with explicit optional bits/counts. Provider digests frame typed group/name or host variants, never Display; selector dimensions are canonical OR-sets (sorted/deduplicated strings) while entry order remains semantic. A builtin carries a registry-owned epoch that moves when behavior moves; a package hash never pretends to cover host code. Empty plan has no digest/header/fingerprint frame and appends no pass, preserving historical schedule/errors/bytes.

7.4 Tier 2 — the pass tier: full IR access behind compiler_internals

78Decision (owner ruling 2026-08-25; implemented through R6.5-D). A plugin may be a full compiler extension — a pass with complete access to the packages' IR, free to change it arbitrarily: new optimisations, new frontends, new backends, the LLVM posture verbatim. Users can build the compiler onward from here. The price of the power is explicitness: the pass tier exists only behind a dedicated manifest flag, only under host activation, only in-process (builtin/native), and always on the record (§7.4.3). D1 exposes exact selected native backends through vibe extensions compile; D2 commissions a genuine installed dependency whose first-class TXT frontend feeds its JSON backend. Evidence: ee7f6f2d, def9909a, 56307492.

7.4.1 The IR — five named levels, lowered progressively

79The compiler's IR is multi-level (the MLIR shape, because compilation is progressive lowering): source — one document's raw text plus its spec address; document — one parsed tree; closure — the whole reachable compilation unit and graph; lane — the assembled ordered nodes and provenance; emitted — serialised bytes per artifact. R3 made these explicit typed carriers and R6.2 froze their epoch-1 wire.

7.4.2 Pass kinds — transform, lowering, frontend, backend

  • 80Transform pass — level N → level N (an optimisation): pass = { kind = "transform", level = "closure", after = "qualify" }. Position is by built-in pass name (after / before), exactly LLVM's pass ordering; tier 1's four stages are, mechanically, transform passes pinned to canonical positions — one machine, two doors.
  • Lowering pass — level N → level N+1. The built-in chain IS a lowering sequence: parse (source→document), close (documents→closure, the topo/merge/embed complex), assemble (closure→lane), emit (lane→emitted). A plugin lowering replaces or augments a named built-in: pass = { kind = "lowering", replace = "emit", artifact = "static-xml" }.
  • Frontend — a parse-position lowering registered for new source formats: pass = { kind = "frontend", formats = ["adoc"] } receives source, returns document. The address grammar treats a registered extension exactly as it treats .md/.xml (canonical doc paths strip it), so a new format's documents are first-class spec:// citizens. Native frontend admission and execution landed in 393d519c; 56307492 commissions a real installed UTF-8 .txt document whose physical filename stem becomes its title, whose nonblank physical lines become declaration-order paragraphs, and whose root anchor is root.
  • Backend — an emit-position lowering: receives lane, returns artifact bytes — a new lane serialisation (JSON lane, compact lane, HTML projection) or a replacement of a built-in emitter for one named artifact. Built-in emit passes carry stable names (emit:static-xml, emit:static-md, emit:index, emit:inline) so replacement is precise. Native backend selection, strict byte reply and manager-owned provenance reconstruction landed in 0797c4ca; public exact-backend compilation landed in ee7f6f2d, hybrid pass-plan retention in def9909a, and 56307492 proves deterministic newline-terminated JSON to stdout or atomic file output, with invalid frontend output stopping before the backend.

7.4.3 The flag, and the trust posture of full access

81A contribution enters the pass tier by declaring compiler_internals = true on its [[extension]] table — the owner-mandated flag, a single conspicuous boolean whose presence means "this plugin asked for the low-level API". Without it, pass = … is a validation error. With it: host activation is always required; the activated pass list and positions bind the generated artifact header and freshness; and narration names each internals plugin. Grammar and refusal landed in b2a6efb5; retained owner-plan carriage landed through cf51582a.

82The tier is builtin/native only (§8.5's reasons apply with more force). The flag is a declaration, not a permission dialog (owner ruling 2026-08-25, ##INSTALL-IS-CONSENT): no consent prompt guards it — its job is to make the capability VISIBLE (to vibe extensions, to vibe list, to static scanners, to a human reading the manifest) before anything runs. Installation plus the host's activation line are the whole authorisation, the Clang-plugin posture; the safety spend goes into §3.5's observability instead of gates.

7.4.4 The wire — whole IR in, whole IR out

83A pass crosses the C ABI as one call per pass per compilation: the request carries the full serialised IR of its level (payload.ir, schema-versioned JSON), the reply carries rewritten IR or strict bounded bytes for a backend. No handle API, callbacks or partial views; the manager owns composition, reconstruction and verification. Evidence: c26cd039, 17afb5b6, 1633c8cb, ba639210 and 0797c4ca.

7.4.5 The IR schema is a versioned contract, and the verifier runs between passes

  • 84The IR wire schema carries ir_schema = 1; additive growth stays within the version and breaking shape requires a bump. The native manifest declares its schema and mismatch refuses before invoke. Evidence: c26cd039, 17afb5b6.
  • The verifier makes arbitrary mutation survivable: after every plugin pass the level's invariants are rechecked, so invalid output fails at the exact qualified pass before the next pass runs. Production installs the verifier unconditionally for every nonempty pass plan; manifests/tests cannot disable it. Evidence: 8c02dfd8.
  • §7.3's oracle rule extends unchanged: the reference semantics are the built-in pipeline with zero plugin passes, and the recorded transform/pass list is part of artifact identity. Empty pass plans preserve prior bytes and mtimes.

7.5 The IR refactor — the composability precondition

85The mandate's closing sentence is a work item: today the transitions live as one monolithic phase loop (compile_static_inner — parse/topo, merge, embed, qualify, absorb, link, emit in fixed code order) over implicit representations. Before any pass-tier API exists, the compiler is refactored so that: (1) the five §7.4.1 levels are explicit types; (2) every built-in phase is a named first-class pass over them (parse, close, merge, embed, qualify, absorb, link, assemble, emit:*) registered in the same pass manager plugins will use — built-ins are not privileged, which is the composability test; (3) the default pipeline is a declared pass LIST, not control flow. The refactor's gate is byte-identity: the default pipeline over the whole existing corpus must emit byte-identical artifacts before and after — the compiler's own oracles (round-trip, byte-stable lanes) judge it, and no external API ships until they are green.

86Where the compiler lives TODAY (verified against the tree 2026-08-24/25; the refactor's raw material, so an implementer starts from addresses, not archaeology): the phase loop — crates/vibe-spec/src/pipeline.rs (compile_static_inner, with compile_static / compile_static_qualified as its two modes); the #use graph and topo order — use_graph.rs (topo_order_from, source_fold_order); the #source fold — merge.rs (fold_sources) plus pipeline.rs's fold module; #embedembed.rs (expand_embeds, the SectionSource trait every resolver implements); qualification and rename maps — qualify.rs (qualify_contribution, origin_slug); reversible markers — markers.rs (open/close/decompile); addresses and routing — address.rs / resolver.rs; the doc IR — doctree.rs (DocTree) with the format pivot in the separate vibe-specdoc crate (from_markdown/from_xml/to_xml/to_markdown). The lane is INVOKED from vibe-workspace's boot_artifacts (boot_artifacts.rs, boot_artifacts/normal.rs — the normal-lane seed) during install's boot regeneration.

87The refactor must leave the door open to per-level memoization (the Webpack persistent-cache / Turbopack lesson, 2026-08-25 audit): once every pass is a pure function of typed IR, a level's output is cacheable by its input hashes — parse results keyed by source hash (one edited document re-parses one document, not ninety-eight), pass outputs keyed by (input IR hash, pass identity, config). Not built in R3 — today's corpus compiles fast enough — but the pass signatures are designed so that adding the cache later is a wrapper, not a rewrite; the day an expensive plugin pass (an LLM-driven compression) joins the pipeline, this is what keeps the ritual usable.

8. Native extensions — one ABI for both families

8.1 The ABI is C + JSON, never the Rust ABI

88A native handler is a cdylib exposing exactly four ABI-1 C symbols: vibe_ext_abi, vibe_ext_manifest, vibe_ext_invoke, and vibe_ext_free. The Rust ABI is never the wire; versioned JSON request/reply roots carry lifecycle, compiler or mechanism-family values, one family per image. ABI/manifest admission precedes invoke, replies are bounded, plugin panic is contained, and the host frees owned reply memory exactly once. Evidence: fd81a003, bfaea140, 9f7b8854, ed6e7c2a, 9465291e, 24b1fe4a.

  • 89vibe-ext publishes safe lifecycle, compiler and mechanism-family author macros over the same four ABI-1 symbols; one cdylib belongs to one family. Authors never touch unsafe. Evidence begins at bfaea140 and extends through 24b1fe4a.
  • The quarantined loader checks ABI and the exact bounded manifest before invoke, caches one canonical immutable image with a strong library handle, contains panic at the plugin macro boundary, caps replies and frees each allocation exactly once. Evidence: 9f7b8854, 126dfc0b, 24b1fe4a.

8.2 Where native extensions build — in place, amending PROP-024 §2.3

90Per the mandate, a dependency native builds under its exact materialised provider root and an authored package under its own live root. Cargo target/ remains outside source hash, shippable tree and .vibe-slot.toml; R1 preserves that unrecorded output and mtimes. The shared artifact record and immutable-image publication bind the selected result. Evidence: 1baac652, 332f8e28, 06f8f465.

91The build phase prepares every selected source-native group before authored artifact targets, then replays all pending compiler lanes before later phase rows. Compiler natives never build lazily mid-compile; prebuilt-first selection may require no Cargo. All providers/conflicts preflight before writes. Evidence: 332f8e28, 06f8f465, 0435b937.

92Closed-source plugins ship prebuilt. The exact current-platform prebuilt wins; otherwise declared source may build under the provider root, and absence refuses rather than skipping. Prebuilt bytes are ordinary package content and both paths enter the same record, immutable-image, ABI/schema and loader admission laws. Platform projection occurs before build/load. Evidence: 1baac652, 269bec0d, bb50aeab.

93Install precedes build and convergence is truthful. Install never runs Cargo or native replay: missing buildable compiler natives produce ordered pending evidence in each affected node/package-unit artifact while available transforms still execute. Build preflights every selected provider group before writes, builds each source group once, prepares and publishes strict replay for the exact retained owner world, then runs authored artifact targets and phase rows. A failure preserves the appropriate pending or already-converged owner boundary; retry converges exactly, removes pending only after real execution, and a fresh repeat preserves bytes/mtime. Evidence: 362d7c8d, 06f8f465, 0435b937.

8.3 The road not taken now

94WASM handlers — deferred, not rejected: a wasm component would buy sandboxing (the PROP-020 §4 accepted-risk posture could tighten) and cross-platform artifacts, at the cost of a runtime dependency and awkward filesystem/process access — exactly the operations phase handlers exist to do. When the sandboxing need fires (third-party plugin ecosystems beyond the allow-list), this anchor is where the design resumes. Until then the trust gate is the boundary, exactly as PROP-020 records it.

8.5 Why compile: takes only builtin and native

95A compile: transform runs inside lane regeneration, potentially per document. Script/binary spawn and agent nondeterminism are therefore forbidden: compiler contributions execute only as builtin or admitted native handlers. R5/R6 retain this closed production dispatch.

9. The incremental-install revision — materialise becomes a diff

9.1 The defect, named from the code

96Historical defect, closed by R1. The former materialise_with cleared and recopied a whole slot, so an edited in-workspace package erased unrecorded target/ output. 6d606ef2 introduced strict slot ownership records, 1cf4f189 replaced the wipe with an owned per-file diff, and 6a7f750d let the current mutable-source hash earn a record-backed skip. Native and binary outputs outside the record now survive refresh by law.

97Current materialisation map (R1 accepted). crates/vibe-workspace/src/vibedeps/slot_record.rs owns the strict record; slot_diff/report and slot_cow own reconciliation, exact change accounting and hardlink-safe replacement; vibedeps/derived.rs projects transformed identity into the same record while retaining only a legacy .vibe-derived.toml reader; install.rs applies presence, representation, mutable-source and SlotIntegrity::TrustPresence|Verify gates; vibe-install/src/slot_verify.rs supplies record-aware verification; freshness.rs owns pin holding; and install/hooks_run.rs consumes the exact post-install change plan so hooks run once only after a nonempty payload change.

9.2 The slot record — the footprint becomes a file

98Every materialised slot has one strict slot record.vibe-slot.toml at the slot root: schema, source identity, representation identity and sorted per-file ownership/SHA-256 rows for every path the materialiser wrote. Transformed slots carry PROP-045's source/recipe/overlay/derived identity and per-file provenance in this same record; .vibe-derived.toml is a read-only legacy input, never newly written. The record is outside source content identity and is written last. Evidence: 6d606ef2 and 1cf4f189.

9.3 The diff algorithm — write what changed, delete what was ours, touch nothing else

99Re-materialising a slot that carries a record: (1) compute the incoming footprint (path→hash) from the source; (2) for each incoming file — recorded hash equal ⇒ leave the file untouched (no write, no mtime change); differing or unrecorded ⇒ write it; (3) for each RECORDED file absent from the incoming footprint ⇒ delete it (and now-empty parent dirs, best-effort); (4) any on-disk path NOT in the record — target/, node_modules/, any build output — is never touched; (5) write the new record last. A slot with no record (legacy) pays one final full replacement and comes out recorded; a malformed record is a hard error, never authority to wipe unknown paths. vibe clean remains the only whole-slot wipe (PROP-053 unchanged). Evidence: 1cf4f189.

  • 100Step 2's "untouched" is a law, not an optimisation: unchanged files keep their bytes AND their mtimes, so cargo's fingerprinting sees an unchanged workspace and the native/binary rebuild inside the slot is a no-op. This single property is what makes «инкрементальность — супер важный кусок» hold end-to-end: edit one prompt file in a package → one file rewritten in the slot → cargo rebuilds nothing. Evidence: 1cf4f189.
  • Mutable sources get a freshness gate instead of a wipe: for a PROP-011 §2.6 file:// working-tree source, install re-hashes the source's shippable tree and compares it with the valid record's source identity. Equality may skip materialisation; mismatch flows through the §9.3 owned diff. Under slot_integrity = "verify", recorded payload drift is still checked and healed. Presence alone never proves a mutable slot current. Evidence: 6a7f750d.
  • Drift repair gets cheaper, not weaker. PROP-020's reset guarantee (hook edits never compound) is re-based: "reset" = the §9.3 diff restores every RECORDED file to footprint state, byte-exact, whenever the slot re-materialises; verify-integrity mode compares per-file against the record and heals only divergent files. Hooks rerun exactly once after any nonempty payload change, including verify repair, and never after an empty diff. Evidence: 4503fdb6 and 9c545f0d.
  • Amendments this section obligates at build time (recorded here so the build session lands them at the owning anchors, statuses moved, nothing renamed): PROP-011 gains the diff-materialise ruling and the mutable-source freshness gate; PROP-020 §2.1 ##RESET-THEN-RERUN gains the record-diff-restore successor note, and its §2.3 trust gate (allow-list, first-run consent, non-interactive abort, --allow-hooks) is superseded by ##INSTALL-IS-CONSENT + §3.5 observability; PROP-022 §2.2's snapshot reset language likewise; PROP-045's manifest is absorbed into the slot record (its facts re-pointed, file format superseded); PROP-024 §2.3 per §8.2; PROP-025's build-consent prompt likewise yields to ##INSTALL-IS-CONSENT.

10. The two commissioning test scenarios

10.1 Lifecycle: the phase-announcer plugin

101A fixture package declares in its own manifest: [[extension]] id="announce" point="phase:build" handler={kind="builtin", name="log"} config={message="hello from {phase} in {project}"} (plus a second execution at phase:test — the multi-execution property under test). E2E: install the fixture into a scratch project, run vibe test, assert stdout carries both rendered lines in phase order, each attributed to the providing package (§3.3 surface rule); run vibe test again and assert the executions report fresh (§4.3 under test); assert vibe extensions --json lists both executions with their provider (§3.5 under test), and that a host [extensions] disable = [...] line silences exactly one of them (§3.3 under test). The log builtin renders {phase}, {project}, {package} from the envelope — proving the envelope reaches handlers, which is the scenario's real point. Commissioned by 3137990c.

10.2 Compiler: the XML whitespace squeezer — a TEST vehicle, not a shipping practice

102A compile:emitted transform (builtin first — proving the declaration/activation/ordering tract; then re-shipped as a native cdylib in a fixture package — proving the §8 build/load/invoke tract with byte-identical output, which is itself the assertion). The algorithm, XML-aware and safe by parent-shape: parse the emitted XML; delete every text node consisting solely of whitespace whose parent element has at least one ELEMENT child and no non-whitespace text child (pure element containers lose their pretty-printing; mixed-content elements — anything carrying prose — are untouched byte-for-byte); comments (the compiled lane's vibe: markers) and CDATA survive verbatim; serialise. Applies to .xml artifacts only. Builtin/native exact parity, pending→one-build convergence, freshness and failure/retry across root/member/package unit are commissioned by 2e6bb266.

103The owner's own caveat, recorded as a norm: inter-tag whitespace stripping is a poor production idea — token savings are marginal and the readability/diffability of the lane (and its reversibility oracle, §7.3) suffer. The transform ships as a test fixture and a mechanics-proof, default-off everywhere; the real tenant of these stages is the future Caveman compression pass at compile:lane, which compresses prose content deliberately rather than shaving markup accidentally.

11. Rejected and deferred alternatives

  • 104Two separate extension grammars (a [hooks]-style table for lifecycle, a Babel-style list for the compiler) — rejected: the mandate's core is one coherent machine; divergence here is the «разброд» the owner named as the failure mode.
  • Adopting Maven's full 23-phase table — rejected: the hyphenated phases exist to sequence JVM build minutiae we do not have; nine phases cover the mandate, and the phase table is extensible behind a ruling when a real between-slot need arrives.
  • dylib with Rust-native symbols (handing &mut DocTree across the boundary) — rejected: no stable Rust ABI exists; every plugin would be pinned to the exact compiler and vibe build, which is undistributable. C ABI + JSON (§8.1) is the only honest wire.
  • Script/binary handlers for compile: stages — rejected (§8.5): spawn-per-document inside install's innermost loop, interpreter availability as a compile dependency, and agent handlers there would break boot-lane determinism.
  • Closure/graph access in the STAGED tier (a tier-1 transform adding/removing #use edges) — rejected as a prompt-injection lever and an auditability break (§7.2 ##NO-CLOSURE-HOOK). Full access moved tiers rather than dying: the 2026-08-25 ruling prices it into the pass tier behind compiler_internals + host activation + the header record (§7.4.3) — an explicit capability, never a silent one.
  • Deferred: an interactive handle/callback C API for passes (the LLVM-C-API genre — the plugin calls back into vibe to walk and mutate nodes piecemeal) — the pass wire is whole-IR-in/whole-IR-out (§7.4.4) until a real pass demonstrably cannot be written that way; the deferral lives behind this anchor.
  • Inline command strings in the manifest — stays rejected exactly as PROP-020 §3 ruled; the script kind keeps the reviewable-file form, and builtin/binary/native are the structured alternatives.
  • Deferred: direct-verb mixing in a chain (##EXISTING-VERBS-STAY); WASM handlers (##WASM-DEFERRED); a per-node visitor API for external transforms (##NOT-FROM-BABEL); pkgrefs scoping non-install phases (##CHAIN-GENERAL).

11.1 Landed package/build/deploy successors

  • 105The strict manifest grammar lowers build/package targets into one dependency-validated artifact DAG. Engine records bind artifact id/kind/path/digest, producer target, logical mechanism, exact provider/version/content, platform and build-affecting input/config/toolchain identity; output existence alone is never freshness. Builtin Cargo selects compiler artifacts only from Cargo JSON messages. Evidence: 2a3f3b44, a22da2a3.
  • Package consumes verified artifact records and mutates no destination. Builtin providers emit the validated one-file static skill, canonical Agent Plugin directory/client projections, and deterministic Windows zip through the same mechanism registry and phase fence. Evidence: a5dc3cbc, 40c53f0a, 0a42456e.
  • Deploy profiles select ordered targets; plan is read-only. The engine owns provider selection, collision locks, intent/checkpoints, receipts, three-digest recovery, inverse sequencing and exact resource ownership. vibe-bin plus isolated Claude/Codex/OpenCode skill/plugin adapters implement plan/apply/verify/recover/remove without touching foreign neighbours. Evidence: 0a42456e, 45d88e80, ae36ac48.
  • Artifact/package and deploy target when.os use the closed windows|linux|macos vocabulary and one injected loading-model OS observation. Global validation remains, then inactive rows project out before provider/source/record/collision/build/load; an active consumer of an inactive producer refuses. Human/JSON output reports deterministic skips and undeploy remains receipt-owned. Evidence: d4edb3ae, 8ff4b711, bb50aeab.
  • A real installed package may displace a builtin deploy mechanism through an exact route/pin. The engine reuses ABI-1, R5 prebuilt/source record and immutable-image carriage, admits the generated mechanism manifest and exact operation, and invokes all six deploy operations while retaining plan/receipt/recovery ownership. Restart plan/recovery/undeploy re-resolve once at the command boundary and exact-compare the durable sidecar binding, existing record/image/digest/path without build, repair, publication or builtin fallback. Evidence: 854707a1, 9465291e, 24b1fe4a, 269bec0d, 370ea177, a24e4aff, d475963c.
  • Closed M-007 successor; the stable anchor name retains the earlier open-state address. Package-supplied Build and Package providers have separate generated four-operation request/reply roots and safe ABI-1 macros, while deploy retains its six-operation wire. The all-role carriage resolves exact pins after OS projection, reuses R5 prebuilt/source records and immutable images, and hands owned prepared bindings to adapters without re-resolution or builtin fallback. Build owns staged output proof, A2 records and provider-fresh rollback; Package resolves the real build record first, then owns input/config/provider fingerprints and file/tree proof. A real installed dependency package supplies two ABI-isolated cdylibs, displaces build:cargo and package:static-file, produces the Build A2 record and consumes it through Package; the second Build invocation reports provider freshness without rewriting exact bytes. Evidence: 00cabac8, 93df9d28, e9388559, 2193e6e7, fe0eb9df, 0ca2b781, 123ac504, 68ce9078, 55cfbf94. Application installation remains a deploy-role concern; dependency materialisation remains the non-mechanism vibe install phase.

12. The proposed build order — six self-standing waves

  1. 106R1 — the materialise diff (§9), accepted. The strict slot record (6d606ef2), owned per-file reconciliation (1cf4f189), mutable-source hash gate (6a7f750d) and nonempty-diff hook schedule (4503fdb6, 9c545f0d) preserve generated native/binary outputs and unchanged mtimes while retaining exact verify/heal behavior.
  2. R2 — the lifecycle engine (§3–§6 minus agent), accepted. The phase table, chain grammar, [[extension]] parsing, envelope, handlers, durable freshness and §10.1 commissioning all execute through one engine.
  3. R3 — the IR refactor (§7.5), accepted. Explicit level types, named builtin passes, the declared default pipeline and verifier skeleton preserve byte-identical baseline artifacts.
  4. R4 — tier 1, accepted. Four canonical positions, owner activation, header/freshness identity, builtin XML minifier and analyzer are commissioned.
  5. R5 — the native tier, accepted. ABI/SDK/loader, source/prebuilt artifact law, pending bootstrap convergence and exact native/builtin minify parity landed through 0435b937 and 2e6bb266.
  6. R6 — tier 2, accepted. R6.1-R6.4 and R6.5 A-C2 implement grammar, schedule/catalog carriage, native frontend/backend execution and mandatory verify-each through 0797c4ca. R6.5-D adds public vibe extensions compile (ee7f6f2d), retains pass plans through hybrid emission (def9909a) and commissions a genuine installed TXT frontend plus JSON backend with the negative matrix and R5 all-owner parity/retry (56307492).
  7. R7 — create and the agent tier (§6.4–§6.5). The real provider seam, selected-world agent execution, strict multi-output result, durable hosted outbox/fingerprint resume and shared CLI/MCP lifecycle surfaces are complete through the all-green R7.4 boundary. Provider presence activates nothing and hosted work spends nothing.
  8. R7.5 — neutral external orchestration mechanics (§4.6). P0 fixes the evidence/optional-provider/vocabulary laws; P1 adds JTD-first input/artifact/run evidence; P2 builds the read-only vibe-specdoc authoring + vibe-facts consumer projection with optional current/carried specmap relations; P3 exposes one thin requirements query over CLI/MCP and proves it beside existing lifecycle control with a fake external PDSA process. No coding agent, automatic repair loop, task-choice heuristic or Plan/Act policy is implemented; a reference agent remains a separate future campaign.
  9. R8 — package/build/deploy substance accepted in implementation. Artifact DAG/records, Cargo, static skill, Agent Plugin, three client adapters, vibe-bin, profiles/intent/receipt/recovery, platform applicability and package-supplied native deploy/build/package providers are implemented. Deploy restart rehydration remains read-only; Build/Package replacement executes through exact prepared carriage and a real installed two-cdylib build→A2→package E2E with no builtin fallback. Evidence through deploy d475963c, then M-007 00cabac8, 93df9d28, e9388559, 2193e6e7, fe0eb9df, 0ca2b781, 123ac504, 68ce9078, 55cfbf94. This node is done. All technical/full/conform/generated/specmap/no-transform and mirror work is complete at 39fa498a34d30c7744c8598c9f1a0673a6aeee62; the PROP root remains impl/work solely because owner final inspection in the current chat is pending.

13. Open questions — the owner's forks

  • 107Naming — CLOSED (owner, 2026-08-25: «окей»): the nine-phase table stands as drafted — generate separate from build, verify in the table from day one, the name create confirmed.
  • Native trust — CLOSED (owner, 2026-08-25): no consent tier of any kind — installation is the consent (##INSTALL-IS-CONSENT), for in-process cdylibs exactly as for scripts, the Gradle/Webpack/Clang norm. The safety budget is spent on §3.5 observability. §8.3's wasm sandboxing note stays the eventual escape if a hostile-ecosystem era ever demands one.
  • Create economics — CLOSED (owner, 2026-08-26): there is no one global create budget. ##LLM-BUDGET defines per-enhancement/per-workload call and token ceilings, lazy spend and mode-specific exhaustion. Exact defaults/manifest grammar remain implementation work at the first consuming enhancement; the policy fork is closed.
  • Deploy first targets — CLOSED (owner, 2026-08-26; extended 2026-09-04): selected genres are project/user agent projections for Claude Code, Codex and OpenCode; VibeVM-owned local tools under ~/.vibe/bin; receipt-owned operator launchers under the VibeVM-controlled ~/.vibe/opt/bin; an explicit custom installer for ordinary applications; registry/marketplace/server/remote providers; and future system-scope package/config/service providers. The first commissioning cut is isolated local projection/vibe-bin/vibe-opt-launcher, never a live publish/server mutation. The commissioned local and native provider statuses are closed at the R8 successor anchors; live remote/system mutation remains future work under ordinary authority boundaries.

13.1 Static files and VibeVM opt launchers

108Owner ruling, 2026-09-04. The package tool:org.vibevm.world/zai-glm-claude preserves the direct z.ai GLM-through-Claude-Code worker contract and installs its Windows launchers into ~/.vibe/opt/bin. Bare vibe install still owns only package materialisation and boot composition; the package's installation procedure completes through its explicit deploy profile. No install hook, manual copy or extension of deploy:vibe-bin may blur that phase boundary.

  • 109package:static-file. The engine-fresh builtin accepts exactly one contained workspace { path } file and exactly one file output whose artifact id is a portable single filename. Config is absent or empty. It opens the source once through the project's capability-relative no-follow/single-link filesystem seam, copies from that held handle into an engine-owned capability-relative staged output, records and independently verifies the ordinary artifact digest, and never interprets or executes the content. It never reopens an already-proven pathname or recursively removes through an unproved ancestor. Directories, links/hardlinks, recorded-artifact inputs, multiple inputs/outputs, non-file output kinds, unsafe filenames and nonempty config refuse before a write.
  • deploy:vibe-opt-launcher. The provider-fresh builtin accepts one recorded file artifact, an empty config, and no inferred destination. Its artifact id is the destination filename and must satisfy the repository's one canonical portable-component law. The only physical resource is <settings-root>/opt/bin/<artifact-id>; with the default settings root this is ~/.vibe/opt/bin. Plan may observe but never mutate it. Source and destination I/O use held capability-relative no-follow/single-link handles, never raw path reopens or symlinkable ancestor walks. Apply publishes atomically; an owned update preserves prior bytes plus mode in engine-owned rollback state and returns a validated handle so a later-target failure restores the earlier launcher. Before that saga inverse mutates the destination, the engine durably binds generation, provider, resource, receipt digest and handle in inverse.json; after an exact independent restore check, the rolled-back receipt owns the restored prior state and only then may the marker retire. A manual byte reversion without that marker remains drift, and forward or inverse recovery preserves the marker and backup across crashes. On Unix, desired/observed/receipt identity binds content plus exact mode 0755; verify requires that full state and recover can restore it rather than failing before recovery. Windows binds content and makes no POSIX-mode claim. Remove touches the file only through receipt ownership and refuses drift. The provider's adapter epoch is part of plan.config_digest, hence the durable plan hash. An unowned occupant is a collision. The provider never edits PATH, reads credentials, parses the launcher, writes a long-lived content store or owns a neighbouring file.
  • The package declares one package:static-file row and one deploy:vibe-opt-launcher row for each launcher file, an explicit windows profile for PowerShell/CMD, and a posix profile for the Bash launcher on Linux/macOS. A consumer first installs the package so its boot and protocol become part of the project, then runs the host profile against the resolved package slot. Only the second phase creates the launcher. Documentation may call the pair the package installation procedure, but must never claim bare materialisation mutated user state.
  • For this time-limited preservation slice, the owner explicitly waives the usual broad mutation campaign. Acceptance still requires parser/provider focused tests, package check, isolated install/deploy/verify evidence and one real bounded GLM worker task; at most one same-cwd correction may follow that live task. A reduced gate is reported as reduced and never relabelled a full panel.

14. Reference — the exact shapes (normative for the build)

110This section exists so that an implementer — human or model — never has to guess a field name, a file layout, or a call sequence from the prose above. Where a shape is deliberately unfrozen it says so; everything else here is normative.

14.1 The [[extension]] table, field by field

111field               req?  type      applies to        meaning / rules
id                  yes   string    all               unique within the declaring manifest;
                                                      the reference key is <group>/<name>#<id>
point               yes   string    all               "phase:<name>" | "slot:pre-install" |
                                                      "slot:post-install" | "compile:<stage>" |
                                                      "compile:pass"
handler             yes   table     all               { kind = "builtin"|"script"|"binary"|
                                                        "native"|"agent", ... } — see 14.4
config              no    table     all               free TOML, delivered to the handler verbatim
auto                no    bool      compile:* only    default false for compile:*; true elsewhere
                                                      and not writable there (§3.3)
inputs              no    [string]  phase:*           extra fingerprint inputs, globs (§4.3)
applies_to          no    table     compile:source/   { packages = [globs], paths = [globs] } —
                                    document, slot:*  engine-evaluated selectors (##CONTRIB-SELECTOR)
compiler_internals  no    bool      compile:pass      REQUIRED true to use point "compile:pass";
                                                      forbidden elsewhere (§7.4.3)
pass                no    table     compile:pass      { kind = "transform"|"lowering"|"frontend"|
                                                        "backend", level|from/to, after|before|
                                                        replace, formats, artifact } (§7.4.2)
when                no    table     all               applicability guard — UNFROZEN, future ruling

112Handler sub-shapes: builtin{ kind, name }; script{ kind, base } (base path without extension, PROP-020 §2.2 resolution); binary{ kind, name } (a [[binary]] name); native{ kind, crate_dir? , prebuilt? } (at least one of the two, §8.2); agent{ kind, prompt } (a spec:// address the package resolves).

14.2 .vibe/lifecycle.toml — the run/freshness record

113schema = 1
[run]                       # last run, whole-chain
requested = "build"
chain = ["validate", "install", "generate", "build"]
started = "2026-08-25T12:00:00Z"        # clock is an input, injected — never Date::now in tests

[execution."org.vibevm.x/y#announce"]   # one table per execution, keyed by reference key
phase = "build"
fingerprint = "sha256:…"                # §4.3: hash over the execution's declared inputs
status = "ok"                           # ok | fail | skip | fresh | delegated
duration_ms = 12
artifacts = [ { id = "…", path = "…", kind = "…" } ]

114Machine state: inside .vibe/ (shippable-tree denylist), never committed, never hashed, survives vibe clean (§4.3), erasable at will — losing it costs one full re-run, nothing else. Schema-versioned like every state file we own.

14.3 .vibe-slot.toml — the materialised-slot record (§9.2)

115schema = 1
source_hash = "sha256:…"     # content_hash of the shippable tree that was materialised
spec_format = "xml"          # mixed | markdown | xml — absorbs .vibe-derived.toml's role
converter_recipe = "…"       # present iff transformed (PROP-045 genre)
derived_hash = "sha256:…"    # present iff transformed
overlay_hash = "sha256:…"    # present iff a facts overlay applied

[[file]]                     # one row per file the MATERIALISER wrote — the footprint
path = "vibevm/vibespecs/boot/20-stack.md"    # slot-relative, forward-slashed, sorted
sha256 = "…"
# for transformed slots rows also carry: source = "…", disposition = "converted"|"copied"

116Lives at the slot root and is excluded from source content identity. Unknown fields refuse (deny_unknown, schema bump to extend). The §9.3 diff reads the old record, computes the incoming footprint, and writes the new record LAST — a crash between steps leaves a slot the next run heals (recorded files re-diff; unrecorded paths were never touched). .vibe-derived.toml remains only a read-only compatibility input when no new record exists; a valid new record wins and a malformed one never falls back.

14.4 Handler wires, end to end

  • 117script: interpreter per PROP-020 §2.2 (.sh via bash first, .ps1 via PowerShell on Windows fallback); cwd = slot for slot: points, project root for phase:; env = the VIBE_* set PROP-020 documents plus VIBE_CONTEXT (absolute path of the envelope JSON written into scratch) and VIBE_REPLY (absolute path where a reply JSON MAY be written); exit 0 + no reply file = {"status":"ok"}; non-zero exit = fail regardless of reply file; streams go to the user live.
  • binary: the artifact is resolved via PROP-025 (DeclaredBinary::artifact, built if absent); envelope JSON on stdin (closed after write); reply JSON expected on stdout (whole stdout parsed as one JSON document; stderr streams to the user); non-zero exit = fail; a malformed reply = fail naming the execution.
  • native, one invocation, seven steps: (1) resolve artifact — prebuilt-for-platform, else built cdylib, else refuse; (2) load (libloading), once per run, cached by path; (3) vibe_ext_abi() — must equal a supported ABI version or refuse with «rebuild: vibe build»; (4) vibe_ext_manifest() — parse, check the execution's id/point/ir_schema are declared; (5) vibe_ext_invoke(req, req_len, &resp, &resp_len) under catch_unwind at the plugin macro's boundary — request/response are UTF-8 JSON byte buffers, envelope in, reply out; non-zero return or panic = the execution fails, vibe survives; (6) parse reply; (7) vibe_ext_free(resp, resp_len). The vibe-ext helper crate's vibe_extension! macro generates all four symbols from a safe fn handle(Context) -> Reply.

14.5 The agent handshake, on the wire (§6.5)

118# .vibe/agentic/outbox/<run-id>/<execution-id>.md   — one task file per agent execution
---
run: "<run-id>"
execution: "org.vibevm.x/y#draft-guide"
phase: "create"
outputs:                       # the contract vibe validates on resume
  - path: "docs/guide.md"
    accept: "non-empty file"
---
<the resolved prompt document, then the envelope's prose projection>

# printed to stdout, then the phase exits with status `delegated`:
```vibe-agent-tasks
run: <run-id>
tasks:
  - .vibe/agentic/outbox/<run-id>/draft-guide.md
resume: vibe <requested-phase>
```

119Resume is the SAME phase command re-run: the engine adopts the exact run_id in .vibe/lifecycle.toml (engine status delegated), validates each task's declared outputs, marks satisfied executions done, re-parks unsatisfied ones (idempotent), and continues the chain. A fresh/displacing identity never services that run's continuation. No dedicated resume verb; the default-lifecycle command's lifecycle --force deliberately displaces/re-parks (not Reinstall's unrelated materialisation --force, which may adopt the same run). Repetition beyond that one state transition belongs to an external orchestrator, never a hidden coding-agent loop (§4.6).

14.6 Historical IR skeletons preceding the frozen tier-2 wire

120source:   { "ir_schema": 1, "level": "source",
            "doc": { "address": "spec://grp/pkg/path#", "format": "md", "text": "…" } }
document: { "level": "document",
            "doc": { "address": "…", "tree": { "title": …, "sections": [ … ],
                     "facts": [ { "id": "…", "status": "…", "text": "…" } ],
                     "directives": [ { "kind": "use|source|embed|spec", "target": "…" } ] } } }
closure:  { "level": "closure", "seed": "spec://…",
            "nodes": [ { "key": "…", "origin": "grp/name", "address": "…",
                         "body": "…", "renames": [ … ] } ],        # topo order
            "edges": [ { "from": "…", "to": "…", "kind": "use|source|embed" } ] }
lane:     { "level": "lane", "artifact": "static-xml",
            "entries": [ { "origin": "…", "slug": "…", "body": "…", "markers": … } ] }
emitted:  { "level": "emitted", "artifact": "static-xml", "bytes_b64": "…" }

121These skeletons are the historical vocabulary predecessor, not the current exact field contract. R6.2 derived the epoch-1 JTD wire from the R3 domain types and froze its generated/domain conversion through c26cd039 and 17afb5b6; ir_schema = 1 is admitted before an external pass loads. The generated schemas and ##WHOLE-IR-WIRE govern current field sets.

14.7 R7.5 evidence and requirements wires

122P1 freezes the following vocabulary as strict JTD and registers/corpora/code-generates it before runtime use. observed_at is excluded from evidence_id; the id is a domain-separated digest over every other canonical identity/comparison member. measured values come from durable execution state; observed values are recomputed at verify. Optional members are absent, never null.

123{
  "evidence": 1,
  "evidence_id": "sha256:…",
  "status": "matched|stale|missing|unavailable|unstable",
  "observed_at": "…",
  "run": {
    "run_id": "32-lower-hex", "selected": ".|member/path",
    "requested": "verify|package|deploy", "chain": ["validate", "…"],
    "started": "…"
  },
  "inputs": [ {
    "execution": "qualified-provider#id", "phase": "build|test|create|verify",
    "declaration_fingerprint": "sha256:…", "patterns": ["src/**"],
    "measured_run_id": "…",
    "measured": { "algorithm": "sha256:vibe-input-manifest-v1", "digest": "sha256:…", "files": 7, "bytes": "1234" },
    "observed": { "algorithm": "sha256:vibe-input-manifest-v1", "digest": "sha256:…", "files": 8, "bytes": "1400" },
    "status": "matched|stale|missing|unavailable|unstable",
    "reason_code": "…"                                      // optional
  } ],
  "artifacts": [ {
    "id": "…", "kind": "file|directory|…", "path": "project/relative/path",
    "measured_run_id": "…",
    "measured": { "algorithm": "sha256:file-v1|sha256:tree-v1", "digest": "sha256:…" },
    "observed": { "algorithm": "sha256:file-v1|sha256:tree-v1", "digest": "sha256:…" },
    "status": "matched|stale|missing|unavailable|unstable",
    "reason_code": "…"                                      // optional
  } ]
}

124P1 froze the strict metadata-only epoch-1 requirements report. The 2026-09-09 terminal-artifact amendment advances it to epoch 2 solely to add the optional authored requires set and the corresponding observation-id frames. It still does not embed verification evidence or a terminal verdict into fact rows: lifecycle evidence and fact/relation observations remain independent roots an external orchestrator may join by full address/edge and exact observation identity.

125{
  "requirements": 2,
  "observation": {
    "observation_id": "sha256:…", "observed_at": "…", "selected": ".|member/path",
    "source_digest": "sha256:…", "lifecycle_run_id": "…"     // optional
  },
  "query": { "address_prefix": "spec://…", "limit": 100, "relations": false },
  "sources": [ {
    "source": { "kind": "host|package", "package": "group/name" },
    "state": "available|unavailable|invalid|orphaned",
    "digest": "sha256:…", "reason_code": "…", "adoption_entries": 1 // optional by state
  } ],
  "relation_sources": [ {
    "package": "group/name", "state": "not-requested|current|carried|stale|unavailable|invalid",
    "provenance": "fresh-project-map|carried-package-map|none", "reason_code": "…" // optional
  } ],
  "rows": [ {
    "address": "spec://group/name/path#fact",
    "source": { "kind": "host|package", "package": "group/name" },
    "authoring": { "presence": "marked|unmarked", "status": { "stage": "…", "state": "…" },
                   "requires": ["specification", "…"] }, // optional; absent = unclassified
    "adoption": { "presence": "not-applicable|absent|indeterminate|recorded", "status": { "stage": "…", "state": "…" } },
    "relations": [ { "verb": "implements|verifies|documents|deviates|informs", "symbol": "…", "provenance": "authored|generated|proposed", "file": "…", "line": 1 } ]
  } ],
  "truncated": false
}

126The shared library query is projected as vibe requirements [--address-prefix <spec://prefix>] [--limit <N>] [--relations] [--json] [--path <project>] and MCP requirements_query({address_prefix?, limit?, relations?}). Default limit is 100, hard maximum 256; zero, overflow, wrong types and unknown members refuse before filesystem access. MCP has no path member because server context is authority. With relations = false no specmap engine/map is loaded. Existing vibe verify --json and lifecycle_run({phase:"verify"}) are the control/evidence surfaces; R7.5 adds no second lifecycle executor and no evidence verb merely to duplicate their generated root.

15. Glossary — the words this document leans on

127slot            one package's materialised directory: vibevm/vibedeps/<group>.<name>/<version>/
world           all resolved packages of a workspace, as pinned by vibe.lock
materialise     copy a package's shippable tree into its slot (today: wipe+copy; after R1: diff)
shippable tree  a package's directory minus build output (.git/.vibe/target/node_modules/.vibeignore)
boot lane       the generated agent-entry artifacts: vibevm/vibespecs/boot/STATIC.* + INDEX.md + INLINE.*
seed            the spec address a normal-format package's boot snippet points the compiler at
closure         every spec node reachable from the seed via directives; what the lane contains
lane            the assembled single-document projection of the closure, marker-wrapped, reversible
origin          the package (or host) a node was authored in; qualification prefixes slugs with it
qualify         rewrite a node's anchors/links under its origin so merged nodes cannot collide
absorbed node   a node whose text is contained in another included node; emitted once (READ-ONCE)
fingerprint     a hash over an execution's declared inputs; equality means skip ("fresh")
contribution    one [[extension]] row: a handler bound to an extension point, with config
execution       one scheduled run of a contribution (Maven's execution; the id is its name)
handler         how a contribution runs: builtin | script | binary | native | agent
envelope        the versioned JSON context every handler receives (§5)
pass            tier-2 unit of compiler extension: whole-IR-in → whole-IR-out at a named position
lowering        a pass that moves the IR down one level (source→document→closure→lane→emitted)
frontend        a parse-position lowering registered for a new source format
backend         an emit-position lowering producing artifact bytes
kind-preset     the built-in contribution table selected by manifest kind + installed stack (§4.5)
slot record     .vibe-slot.toml — the per-file footprint the R1 diff materialiser keeps
machine cache   ~/.vibe/cache — the per-machine package store (PROP-010); never touched by clean

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/common/PROP-054-lifecycle-and-extensions

.md.xmlllms.txt