PROP-024 — Code-bearing packages (a package is a project)
01Status: IMPLEMENTED (proposed 2026-06-27, owner-directed; verified against
the tree 2026-07-25 by the spec-actualization campaign — this very session boots on a
vendored toolchain delivered exactly this way). Makes a vibe package able to
ship runnable code, not only prompt content, so the discipline's verification
tools (the conform checker, the specmap/specmark traceability engine) can live
inside the discipline packages instead of being hardcoded in the vibevm
workspace. A consumer who installs stack:org.vibevm.ai-native/rust-ai-native-lang then has
the working checkers, not a prose description of them.
02Related: PROP-002 §2.1
(content-hash identity — re-scoped here to the shippable tree),
PROP-007 (workspace +
vibedeps/), PROP-009 §2.1/§2.3
(the verbatim materialise step + the boot path it emits),
PROP-011 §2.6
(in-workspace file:// mutability — the dev loop that re-materialises edited
package source), PROP-020
(the post-install build hook), PROP-022 §2.2
(the copy-mode tree — re-scoped here), the discipline mechanism specs
ENGINE-CONFORM and
PROP-014
(the tools that relocate; the specs themselves now ship in
flow:org.vibevm.ai-native/core-ai-native — spec://org.vibevm.ai-native/core-ai-native/mechanisms/…).
03Owner sanction: PROP-024 reshapes the owner-frozen VIBEVM-SPEC.md (§4.2
layout, §7.2 package contents, §7.3 manifest, §7.4 identity, §12 linter, §13.1
package layout). The VIBEVM-SPEC.md edits required explicit owner sanction; it
was granted 2026-06-27 — the same precedent as
PROP-009 §5 item 8.
1. Motivation
1.1 The problem — a package can describe a tool but not ship one
- 04The discipline packages (
flow:org.vibevm.ai-native/core-ai-native,stack:org.vibevm.ai-native/rust-ai-native-lang,…/typescript-ai-native) carry the manifesto, the guides, and the nine pattern cards — prompt content. - But the machinery that makes the discipline real — the conform checker
(Class-F/G rules, the file-length budget, the unwrap ban) and the
specmap/specmark traceability engine — is hardcoded as crates inside the
vibevm workspace (
crates/conform-core,crates/conform-frontend-rust,crates/specmark,crates/specmap-core, thecargo xtask conform/specmapdrivers).
- 05Install
stack:org.vibevm.ai-native/rust-ai-native-langtoday and you get a description of checkers you do not have. To actually run the discipline you would have to re-implement the very tools vibevm already wrote. - The discipline is therefore not distributable: its strong-author artifacts (guide, cards) ship, but its runtime (the checkers) does not.
- This is the gap that, left open, makes spec-conformance "fall apart" for any consumer that is not vibevm itself.
- 06The package format is the cause: a package is defined as a bundle of prompt
files (
VIBEVM-SPEC.md§7.2 — "vibe.toml, README.md, other content files referenced by the manifest"), materialised verbatim. - There is nowhere to put code.
- The identity/materialisation machinery (
content_hashover every file, full-tree copy) actively fights it — a Rust crate'starget/would make identity non-deterministic and the copy ruinous.
1.2 The shape — a package is a project
07A vibe project already has the right shape: an authored spec/ corpus
(VIBEVM-SPEC.md §4.2 — "spec/ is the spec directory") plus arbitrary code
at the root (Cargo.toml, crates/, src/) plus one vibe.toml. A package
should be the same object, made installable:
08prompt/spec content under spec/, arbitrary code at the root, one vibe.toml.
- 09Then a package can ship its tools; an installed package is immediately usable;
and authoring a package is authoring a project — the same layout, the same
vibe check, the same boot computation. - The discipline's own four-layer model lands cleanly: L1/L2/L3 (manifesto,
guide, cards — prompts) live under
spec/, and L4 (the implemented checkers — code) lives at the root.
2. Decisions
2.1 A package is a project — spec/ for prompts, the root for code
10req r1
11Decision. A package has the identical on-disk shape as a consumer project:
- 12Prompt/spec content lives under the package's
spec/subtree — boot snippets (vibevm/vibespecs/boot/), cards, guides, manifesto, appendix — laid out exactly as an ordinary project'sspec/(VIBEVM-SPEC.md§4.2).[boot_snippet].sourceis aspec/-relative path (e.g.spec/boot/20-stack-rust-ai-native.md). - The package root holds arbitrary code (e.g.
Cargo.toml+crates/) andvibe.toml, exactly as a project root does. Code is optional — a prompt-only package (e.g.discipline-core) simply has no code at its root.
- 13Consequence. Developing a package is developing a project.
vibe checkapplies unchanged (its §12 Check 7 —vibevm/vibespecs/boot/exists and holds only markdown — is satisfied by the package's ownvibevm/vibespecs/boot/).- The package's own boot sequence is computed the same way as any project's.
- There is no package-only directory convention to learn.
14This retires the flat package layout (boot snippets and content at the
package root) that the real packages drifted into; it aligns them with — and
extends — VIBEVM-SPEC.md §13.1's own canonical example, which already places a
package's content under spec/.
2.2 The shippable tree excludes build output
15req r1
16Decision. A package's shippable tree is its directory minus a build-output denylist:
17.git/ .vibe/ target/ node_modules/
- 18plus any glob listed in an optional
.vibeignoreat the package root. - The
content_hash(PROP-002 §2.1), the snapshot copy (PROP-022 §2.2), and the verbatim materialised slot (PROP-009 §2.1) all operate over the shippable tree, never the raw directory.
- 19Why. Identity is the source, never build artifacts: build output is non-deterministic (timestamps, host paths, incremental state) and may be gigabytes — hashing or copying it would make identity unstable and materialisation ruinous, the exact failure PROP-022 §1.1 names for "big in file count".
- A package's source — what its author commits — is precisely what is hashed, copied, and vendored.
- For a reference-backed bridge, the package source is only its
maintainer-authored manifest and adapters. A declared external embedded tree
is independently authenticated and cached; it is not part of the package
shippable tree or package
content_hash, and it is never published as if the bridge maintainer authored it.
- 20"Verbatim" is preserved for the source. PROP-009 §2.1 /
VIBEVM-SPEC.md§13.1 guarantee no path rewriting and no per-file write list — a human reading the package directory sees exactly what materialises. - That guarantee holds unchanged for the shippable tree: build output was never part of the authored tree (it is gitignored in the package's own repository too).
- The denylist formalises "what was never source", it does not introduce selection.
- Considered and rejected: hashing and copying build output too — rejected: non-deterministic and potentially gigabytes, the file-count/byte-count failure PROP-022 §1.1 exists to avoid (
##REJ-HASH-BUILD-OUTPUT); a per-file[ship]/[files]allow-list in the manifest — rejected: it resurrects the per-file write list PROP-009 §2.6 retired, and a denylist keeps "what ships" == "the source", preserving the verbatim guarantee (##REJ-ALLOW-LIST,##VERBATIM-PRESERVED). - Revisit when: a package ships in a language whose build output the four-name denylist does not cover and
.vibeignorealone cannot carry — Python (__pycache__/,.venv/), JVM (build/), Go (vendor/) are the near candidates. The fired state is mechanically observable and already asserted: a package whosecontent_hashdiffers between a clean and a built checkout, which is exactly what##ACC-HASH-EXCLUDEStests. Observation point: that acceptance check, run over the published package set.
2.3 Code materialises, then builds consumer-side into a gitignored target
21req r1
- 22Decision.
vibe installmaterialises the shippable tree — including code — into thevibedeps/slot, as today. - Original hook-placement rule, retained as history. Turning source into a runnable tool is consumer-side and build output never enters the source hash. Its stronger “never write inside the committed slot” clause is superseded by
##BUILD-MECHANISM-SUCCESSOR: provider-roottarget/is legal because the shippable tree and slot record own neither it nor its bytes.
- 23The original generic path remains available: a code-bearing tool package may build via a
post-installhook (PROP-020) whose build output is directed to a gitignored location (e.g.<project-root>/.vibe/<pkg>-target/), never the slot. To let a hook address that location, the hook runner gains aVIBE_PROJECT_ROOTenvironment variable (the workspace absolute root) alongside the existingVIBE_PACKAGE_DIR(the slot) — a small PROP-020 §2.2 addition. - A language-native consumer (vibevm itself is a Rust consumer) MAY instead reference the shipped crates directly through its own toolchain (§2.4) and skip the build hook entirely — the hook is the path for a consumer that wants a ready binary without driving the language's build system itself.
- Lifecycle successor. Declared build targets are selected through the shared mechanism registry and recorded artifact DAG. The builtin
build:cargoprovider runs under the exact package/provider root, consumescargo metadatapluscargo build --message-format=json-render-diagnostics, selects only the declared contained compiler artifact from Cargo's messages, and persists provider/config/platform/toolchain/output identity in the shared artifact record. Native extensions and compatibility-lowered[[binary]]targets may therefore build into the provider's own gitignoredtarget/; that output remains outside the shippable tree and.vibe-slot.tomlfootprint. Package-supplied native Build and Package providers now use separate versioned four-operation wires and safe ABI-1 macros, one all-role prepared carriage, engine-owned staging/records and exact provider-fresh rollback; a real installed package with two ABI-isolated cdylibs displaces both Cargo and static-file defaults, writes the build A2 record and consumes it through Package with no builtin fallback. Evidence:a22da2a3,1baac652,332f8e28,06f8f465, then M-00700cabac8,93df9d28,e9388559,2193e6e7,fe0eb9df,0ca2b781,123ac504,68ce9078,55cfbf94. This lifecycle execution does not redefine dependency materialisation: barevibe installstill materialises package content and is not a build/package mechanism invocation.
24The record-aware reset successor does not own build output: whether a hook writes outside the slot or Cargo writes the provider-root target/ inside it, the path is outside .vibe-slot.toml. Same-slot refresh preserves it and unchanged source mtimes let the build system remain incremental; a version change selects a different current slot. Hook-created unrecorded state retains PROP-020's explicit idempotence boundary.
2.4 Consuming shipped code — external-path-dep, no nested workspace
25req r1
- 26Decision. A code-bearing package carries its own workspace manifest
(for Rust, a root
Cargo.tomlwith[workspace]) — it is a standalone, independently-buildable project. - A language-native consumer that needs a shipped
crate — a proc-macro that compiles into the consumer's own code (the
specmarkcase), or a binary it invokes (theconform/specmapcase) — references it by path into the materialised slot:
27# consumer's root Cargo.toml — one pinned alias, updated once per package bump
[workspace.dependencies]
specmark = { path = "vibevm/vibedeps/org.vibevm.ai-native.rust-ai-native-lang/1.0.0/crates/vendor/core-ai-native-specmark" }
[workspace]
exclude = ["vibevm/vibedeps", "vibevm/vibepacks"] # disclaim the package's own workspaces
- 28The consumer excludes
vibevm/vibedeps/(and, for a self-hosting repo, the in-repovibevm/vibepacks/source) from its[workspace], so the slot's crates belong to the package's workspace, not the consumer's — Cargo forbids a directory living in two workspaces, and this is the standard resolution for a repo that contains a sub-project with its own workspace. - The slot path is version-qualified; pinning it once in
[workspace.dependencies]means a package version bump touches a single line.
29A binary tool (conform, specmap) is run from the package's workspace —
cargo run --manifest-path vibedeps/<slot>/Cargo.toml --bin conform -- … with
CARGO_TARGET_DIR pointed at a gitignored dir (§2.3) — so building it pollutes
neither the slot nor the consumer's own target/.
- 30Spike before the irreversible move. Cross-workspace path-deps and the
excludetopology are validated empirically on the target host (Windows, wherecanonicalize()adds a\\?\prefix and Cargo path handling has sharp edges) before any crate is physically relocated. - The fallback, if cross-workspace path-deps prove unworkable on a host, is §4's rejected-but-retained alternative (the consumer adds the slot crates as its own workspace members) — chosen only on evidence.
- Why: an external constraint, not a preference — "Cargo forbids a directory living in two workspaces, and this is the standard resolution for a repo that contains a sub-project with its own workspace" (
##WORKSPACE-EXCLUDE). Giving the package its own workspace is what makes it "a standalone, independently-buildable project" (##OWN-WORKSPACE) and keeps a version bump to one pinned line (##PIN-ONCE). - Considered and rejected: the consumer adding the slot crates as its own workspace members (no package workspace, no cross-workspace path-dep) — considered, and rejected as the primary model because it denies the package standalone-buildability and couples the consumer's workspace membership to generated
vibedeps/state (##REJ-CONSUMER-MEMBERS). Retained, not discarded: it is the §2.4 fallback, "chosen only on evidence" (##SPIKE-FALLBACK). - Revisit when: cross-workspace path-deps prove unworkable on a supported host — the condition
##SPIKE-FALLBACKalready names, here given its observation point: a clean-checkoutcargo buildfailing to resolvevibedeps/<slot>/crates/<crate>on any of the three platforms of PROP-000 §11##PLATFORMS-TRIO, Windows first (##SPIKE-FIRST). The fired state has a landing place already specified, so reopening is a switch, not a redesign.
2.5 Self-hosting bootstrap — the toolchain is vendored
31req r1
- 32Decision. vibevm consumes its own discipline toolchain from the committed
vibedeps/slot. - Because
vibedeps/is committed (PROP-009 §2.1), a fresh clone builds from a clean checkout with no priorvibe install— the path-dep target (vibevm/vibedeps/org.vibevm.ai-native.rust-ai-native-lang/1.0.0/crates/vendor/core-ai-native-specmark, …) already exists in the tree. - There is no chicken-and-egg: the toolchain a build needs is vendored beside the code that needs it.
- Why: committing the slot is what makes a fresh clone build "from a clean checkout with no prior
vibe install" — the path-dep target already exists in the tree (##CLEAN-CLONE-BUILDS), so there is no chicken-and-egg between the toolchain a build needs and the build that would fetch it (##NO-CHICKEN-EGG). Acceptance already asserts it:##ACC-CLEAN-CLONE. - Considered and rejected:
materialization = "in-place"for the tool packages — rejected:in-placeslots are.gitignored and unversioned (PROP-022 §2.4/§2.7), and the toolchain must be vendored and versioned so a clone is buildable offline (##REJ-IN-PLACE). Publishing the tool crates to crates.io and depending on the published versions — deferred, not rejected: "the installed package is the distribution", and crates.io publication is "an optional later convenience for non-vibe Rust consumers, not a requirement of this model" (##REJ-CRATES-IO). - Revisit when: the committed slot's cost outgrows its guarantee —
git count-objects -vHand the slot's on-disk size showing a clean clone expensive enough to outweigh the offline-buildability it buys (numeric threshold unset — owner 2026-08-01; event-shaped until set) — or the deferred demand arrives: a Rust consumer outside the vibe ecosystem needsconform/specmapwithout installing a vibe package, recorded in theF-NNNfindings ledger so the signal has a place to be observed (##REJ-CRATES-IO).
33The development loop stays ergonomic: editing the in-repo package source under
vibevm/vibepacks/org.vibevm.ai-native/rust-ai-native/… re-materialises the slot automatically on
the next vibe install (PROP-011 §2.6 — in-workspace file:// sources are
mutable), so the consumed vibedeps/ copy tracks the edited source without a
manual rm -rf.
2.6 Placement follows the layer model; the engine split is a follow-up
34req r1
35Decision. The discipline's tools are code and obey the four-layer model: L4 (implemented checkers) ships in the package whose language they check.
- 36For this pass, the entire Rust discipline toolchain — the conform
engine (
conform-core), its Rust frontend (conform-frontend-rust), the Rust traceability macros (specmark,specmark-grammar), the traceability engine (specmap-core), and the designated audit crate (env-audit) — ships instack:org.vibevm.ai-native/rust-ai-native-lang. Its centre of gravity is Rust, and shipping the toolchain whole avoids carving language-neutral cores out under time pressure. - The condition fired.
flow:org.vibevm.ai-native/core-ai-nativewas to stay prompt-only (manifesto, card format, scaffold catalog, RAID, appendix) until a second language actually needed the shared engine. The TypeScript pilot shipped, so core-ai-native now authors the neutral engines (conform / specmap / specmark / mcp cores), which each-langand-mcppackage vendors byte-identically. - Deferred (documented): the language-neutral conform engine (
conform-core) is a genuine L1 artifact — a futureconform-frontend-typescriptwould reuse it unchanged. Extractingconform-coreup intodiscipline-coreis a clean follow-up, taken when the first non-Rust pilot needs it (YAGNI until then). Likewise the neutral half ofspecmap-core(markdown parse, index, ledger, test-gate) versus its Rustrscanfrontend. The end state is symmetric; the ordering is driven by real second-language demand, not built speculatively. Executed — the TypeScript pilot was that demand, and the neutral halves now live in core-ai-native, vendored into each family bycargo xtask sync-engines. - Why: the toolchain's "centre of gravity is Rust, and shipping the toolchain whole avoids carving language-neutral cores out under time pressure" (
##THIS-PASS-WHOLE-TOOLCHAIN); the layer model then decides placement rather than convenience — L4 ships with the language it checks. Recorded the same session the decision was taken (§7##HIST-DRAFT-1, 2026-06-27). - Considered and rejected: extracting the language-neutral cores (
conform-core, the neutral half ofspecmap-core) up into core-ai-native in the same pass — deferred, not rejected, with its condition stated: "taken when the first non-Rust pilot needs it (YAGNI until then)" (##DEFERRED-ENGINE-SPLIT). The deferral has since been honoured: the TypeScript pilot was that demand, the condition fired, and the neutral engines now live in core-ai-native, vendored bycargo xtask sync-engines(##CORE-STAYS-PROMPT-ONLY). - Revisit when: (a successor trigger — the previous one fired with the TypeScript pilot and is spent) a third language family arrives and the neutral engines do not cover it — observed as
cargo xtask sync-enginesbeing unable to vendor a core byte-identically into the new-langpackage, or a third family needing a core the two existing ones do not share. Observation point: thesync-enginestask and the set of*-ai-native-lang/*-ai-native-mcppackages.
3. Manifest / schema surface
- 37
[project]and[package]are equipotent consumer nodes (owner law, 2026-08-23, near-verbatim: «и project, и package могут быть потребительской единицей — в репозитории, где разрабатывается пакет, корневой vibe.toml имеет тип package, и всё там обязано работать точно так же; сам маркер — косметическое свойство для человека или UI; разошлись по возможностям — дефект, один и тот же код обслуживает обе роли»). One grammar, one file, the role declared by its XOR section — and every consumer operation (install, lockfile, boot-lane generation,installed:resolution,vibe facts,spec_format, tree/show) reads the role-blindManifest::consumer_node()view and never matches on the role.[package]gainsspec_formatso a package-rooted dev checkout pins its materialisation like a project. Role-SPECIFIC surfaces stay lawful where the role is the subject (publish ships packages; a package's[requires]is a consumer contract, a project's is its own install set). Landed with twin tests (spec_format resolution and the facts host coordinate answered identically from either role) after the divergence audit: bootgen's self coordinate, the facts host reader, spec_format resolution, the tree model and the workspace-origin name all went through the one view. - No new required manifest field.
[boot_snippet].sourcebecomesspec/-relative (a value change, not a schema change).[package].materializationstayssnapshotfor a vendored code-bearing package. - Optional
.vibeignoreat the package root — newline-delimited globs added to the §2.2 build-output denylist. [hooks].post-install(PROP-020) is the build lever;VIBE_PROJECT_ROOTis added to the hook environment (§2.3).- Original code-bearing-package law: package identity remains
content_hashover the shippable tree (§2.2). Reference-backed bridges extend the lock with separately nested embedded-source pins; those pins authenticate projected upstream bytes but do not become part of package identity.
4. Rejected / deferred alternatives
- 38Hash and copy build output too — rejected: non-deterministic and potentially gigabytes; it is the file-count/byte-count failure PROP-022 §1.1 exists to avoid. Identity is source (§2.2).
- Consumer adds the slot crates as its own workspace members (no package
workspace, no cross-workspace path-dep) — considered; rejected as the
primary model because it denies the package standalone-buildability (a package
would not be a project) and couples the consumer's workspace membership to
generated
vibedeps/state. Retained as the §2.4 fallback if cross-workspace path-deps prove unworkable on a host — a decision made on spike evidence, not by default. materialization = "in-place"for tool packages — rejected:in-placeslots are.gitignored and unversioned (PROP-022 §2.4/§2.7); the discipline toolchain must be vendored and versioned so a clone is buildable offline (§2.5). Snapshot-minus-build-output is the right mode.- Publish the tool crates to crates.io and depend on the published versions — deferred: the installed package is the distribution, so the consumer depends on the slot, not a registry crate. crates.io publication is an optional later convenience for non-vibe Rust consumers, not a requirement of this model.
- A per-file
[ship]/[files]allow-list in the manifest — rejected: it resurrects the per-file write list PROP-009 §2.6 retired. A denylist of build output (§2.2) keeps "what ships" == "the source", preserving the verbatim guarantee.
5. Out of scope
- 39Auto-detecting the build system / language of a code-bearing package —
the package declares a hook or explicit artifact/mechanism target; vibevm
does not infer
cargovsnpm. The commissioned builtin is selected only by the declaredbuild:cargotarget. - Sandboxing the build hook — inherits PROP-020 §4's posture (hooks run with the user's privileges; package installation is the consent and execution is made observable through exact provider/target evidence).
- Out of scope when written; since delivered. TypeScript shipped no
implemented tool to relocate, so its cards kept
specifiedchecker statuses until a TS pilot existed (§2.6 deferral). Thetypescript-ai-nativefamily shipped with its floor, conform and specmap engines — the deferral is closed.
6. Acceptance
- 40A package may carry code at its root and prompt content under
spec/;vibe checkpasses on it as a project (vibevm/vibespecs/boot/markdown-only). content_hash, the snapshot copy, and the materialised slot exclude.git/,.vibe/,target/,node_modules/, and.vibeignoreglobs; identical source produces an identical hash regardless of build state.[boot_snippet].sourceresolvesspec/-relative; the generatedINDEX.mdnamesvibedeps/<slot>/spec/boot/<file>.- vibevm builds from a clean checkout, consuming its discipline toolchain (incl.
the
specmarkproc-macro the product crates compile against) from the committedvibedeps/slot, with no priorvibe install. - An external Rust project can install
stack:org.vibevm.ai-native/rust-ai-native-langand runconform/specmapagainst its own code. - Full
self-check.shgreen; conform 0/0/0; specmap clean.
7. Version history
- 412026-06-27 — draft 1. Owner-directed: make the discipline self-sufficient
by letting packages ship runnable code (
spec/for prompts, the root for code), then relocate the Rust toolchain (conform + specmap/specmark) out of the vibevm workspace and intostack:org.vibevm.ai-native/rust-ai-native-lang. The frozenVIBEVM-SPEC.mdsanction was granted the same session (§0). Decisions taken in the owner session: the prompt directory isspec/(singular, project-identical, notspecs/); the full traceability stack moves alongside conform; conform is productised to run on an arbitrary external project (config-driven, not vibevm-hardcoded);conform-coreships in the Rust stack now with the L1 engine-extraction deferred (§2.6).