Skip to content

Semantic-Model Packs

Semantic-model packs are Bifrost’s versioned interchange format for API facts that do not come from workspace source, declarative facts emitted by framework or generator behavior, and reviewed external procedure behavior. A producer can construct the public Rust model directly or load reviewed YAML or JSON. Both paths compile through the same validation and canonicalization pipeline.

Bifrost-packs is the home for open semantic packs and policy rules, including authoring contributions, generation recipes, and independently versioned releases. The engine retains compilation, installation, and activation tooling.

Compilation alone does not install, store, match, or activate a pack; those operations belong to the catalog and generation-scoped runtime described below.

This page explains how packs work and how to use them. The Semantic Pack Reference lists every pack field, every .bifrost/packs.json field, the shipped packs, and every pack command.

Current runtime boundary: Bifrost can compile, defensively decode, install, strictly activate, and match semantic-model packs for one analyzer generation. A successful runtime acquisition also publishes an immutable declaration overlay for normal navigation and query result surfaces. Procedure-summary payloads can contribute exact external-call transfers to value-flow and taint when an embedding supplies the catalog and activation request to the production policy runtime. Workspace source bodies take precedence; missing, conflicting, incompatible, or incomplete summaries stay visible in the analysis outcome.

Together, the catalog and runtime can install, select, activate, account for, quarantine, and garbage-collect packs while keeping matching generation-local.

Packs do not contain executable code, arbitrary templates, fake source, or unbounded evaluator inputs. Generator expressions are bounded trees of literals, declared captures, ordered concatenation, named ASCII case transforms, and conditionals that compare two expressions for equality or a prefix. Procedure summaries are bounded typed records, not executable models or source-text matching rules.

New native packs use schema 9. Their compatibility object contains toolchains and omits bifrost; reader support for the native schema determines format admission. An engine-version field in a schema-8 or schema-9 pack is rejected. Producer versions still identify the generator and remain part of provenance.

Historical native schemas 2 through 7 retain their required, valid compatibility.bifrost range and enforce it during selection and activation. Missing, malformed, null or mixed compatibility forms are rejected. Existing pack bytes and content digests are preserved when the catalog is upgraded.

For every schema, toolchain requirements, artifact identity, selectors and integrity checks continue to apply. Reading the format does not establish complete model coverage or support for every semantic capability; partial results retain their completeness diagnostics.

CLI policy runs and every SearchToolsService session (the MCP server, one-shot --tool and --query-file runs, and the Rust and Python libraries) share one workspace activation contract: .bifrost/packs.json. The document is optional. When present, it must use schema version 1 and must list ecosystems:

{
"schema_version": 1,
"catalog": ".bifrost/semantic-pack-catalog",
"ecosystems": ["jvm", "python"],
"enable": ["org.example.jdk-21"]
}

ecosystems names the dependency ecosystems to discover. It is required. An empty array explicitly disables ambient dependency-pack activation. An unknown or duplicate label is an error. When the document is absent, the shared host path selects every ecosystem that serves a language present in the workspace. An explicit enable entry can satisfy a pack’s review_required gate; it cannot bypass compatibility checks. Activating a pack is the decision to use its models: no separate review receipt exists.

A retained source-to-sink path may establish a definite finding when every summary it relies on comes from an active pack, is complete, and binds exactly to the call’s target and arguments. A partial, misbound or conflicting summary cannot. The finding’s proof reasons retain the authored model dependencies (model identity, content digest and contract version), so a reader can see that the result rests on a model. Raw authored transfer proof stays authored, and a conclusive run relying on those summaries reports proven_by_summary. Genuine gaps and truncated evidence remain separate limits. The workspace pack document reference lists every field, the nine ecosystem labels, the size limits, and the optional python_runtime_environments bindings.

catalog is a workspace-relative catalog directory. Omitting it stores locally generated packs in the repository’s versioned .bifrost/cache catalog, shared with linked worktrees and relocated by the normal Bifrost cache environment overrides. An analyzer explicitly built with ephemeral storage instead uses a delete-on-drop catalog, so a scoped or test session does not create .bifrost inside the workspace. Supplying catalog or a cache environment override is an explicit opt-in to persistent reuse. Default persistent catalogs also reuse exact generated productions through the user’s machine cache. Only the requested generated pack and its extraction accounting are copied into a workspace’s catalog; workspace models and activation settings remain local. Host reuse refreshes retention, and publishing a new production runs bounded cleanup of generated packs unused for at least seven days. Active transfers hold leases. If the optional host cache cannot accept a publication, the verified local copy remains usable and a later hit retries publication. Set BIFROST_SEMANTIC_PACK_CACHE_ROOT to choose an explicitly shared catalog instead. A cold cache still generates each pack from the exact local artifact; later sessions reuse that verified production. Shard validation is cached by exact content, validator version and decode limits; every load still checks content integrity. On Unix, unchanged JDK source ZIPs also avoid rereading the archive using file identity and change timestamps. Other platforms retain exact reads. The analysis catalog and activation APIs do not download packs or dependencies. Hosts acquire compatible open content into their persistent cache; see Cached and offline installation.

Java rules that use reviewed JDK return-value obligations also require an explicit source-tree binding in .bifrost/jvm-toolchains.json:

{
"schema_version": 1,
"source_toolchains": [
{"source_root": ".", "jdk_home": "/opt/jdk-21"},
{"source_root": "preview-module", "jdk_home": "/opt/jdk-22"}
]
}

Source roots are workspace-relative; . binds the entire workspace. JDK homes may be absolute or relative to the workspace. The deepest matching source root wins. Equally specific bindings to different homes remain ambiguous. Every host reads these bindings when it builds the workspace, and an invalid document makes the build fail. Restart a running host after changing them. Missing or ambiguous bindings leave reviewed result obligations incomplete. The reference lists the fields and limits. Discovering a JDK through JAVA_HOME alone does not select it for these rules.

The selected installation must provide supported declaration artifacts such as lib/src.zip. Bifrost joins the exact artifact used to resolve the call with the reviewed model’s compatible activation evidence. The initial JDK 21 pack covers String.trim() as a pure transformation and Files.deleteIfExists(Path) as a status result. For the latter, false means the file did not exist; I/O exceptions are separate. A discarded-status note does not establish that the caller intended to handle that status.

The same transaction mints workspace evidence the reviewed runtime packs need. It mints one intrinsic language row per present language. The row’s ecosystem is fixed per language, for example maven for Java, Scala, and Kotlin, and pypi for Python; the reference lists all of them. A workspace that contains Python also gets one row that carries the reviewed Python process-input profile digest as its configuration. That row activates bifrost.python-process-inputs (os.environ and sys.argv) without a version pin, because the Python standard library defines these inputs for every conforming implementation.

A workspace whose root package.json (engines.node or volta.node), .nvmrc, or .node-version pins the reviewed Node revision 22.11.0 gets one Node artifact row per present JavaScript/TypeScript language. The row carries the reviewed distribution-archive digest and profile configuration that the shipped runtime-values packs require. A workspace that pins another revision, or pins nothing, mints no such row; the pack stays incompatible and the activation report’s reason names the artifact coordinate and the reviewed predicates it requires. Only a declaration of the exact reviewed revision counts — a range such as >=22 or a moving label such as lts/* names no reviewed artifact.

Open content is published by Bifrost-packs in separate packs/vX.Y.Z and rules/vX.Y.Z streams. Engine vX.Y.Z releases retain installer tooling but no longer generate or publish the semantic-pack bundle. Pack versions advance independently of the engine version.

Each release’s pack-release.json records its immutable source commit, required schemas and semantic capabilities, integrity and behavior evidence, and artifact sizes and SHA-256 hashes. Manifest schema 2 checks the consumer’s supported RQL, policy, catalog, and native-model versions; engine versions are provenance and do not gate compatibility. Rules may pin an exact semantic-pack dependency; resolve it rather than selecting an unrelated latest bundle. Default selection requires verified integrity and behavior that has not failed. Pending or limited behavior remains visible in the receipt and does not establish complete coverage. Historical schema 1 releases retain their original engine bounds and qualification requirements.

The bifrost executable reads its engine profile in-process and performs release discovery only for commands that activate policy or MCP packs. It enumerates both release streams, validates the exact rules-to-packs pin, and selects the newest complete eligible pair. bifrost pack-engine-profile continues to print this build’s profile as JSON for inspection; the reference lists its fields. The selector verifies the cached manifest inventory on every use, refreshes it after one hour, and hash-checks cached or downloaded artifacts before safe extraction.

Set BIFROST_OPEN_PACKS_OFFLINE=1 to use only the verified cache. Set BIFROST_OPEN_PACKS_REFRESH=1 to ignore the cached manifest inventory and discover current releases; these controls cannot be combined. BIFROST_LAUNCHER_AUTO_INSTALL=0 also disables pack downloads. If no eligible pair exists, or network access is unavailable and the cache is empty, the binary reports a diagnostic and continues with embedded packs. Invalid profiles, release metadata, and corrupt cached content fail explicitly.

For manual installation, download, verify, and safely extract a native bundle, then install it with the running engine’s portable command:

Terminal window
bifrost install-semantic-packs /path/to/bifrost-semantic-packs /path/to/cache

The command checks the native index, manifests, shards, and generation identity, and installs into the engine’s versioned catalog under the cache root. Hosts can instead import the extracted bundle through the facade bootstrap:

Terminal window
export BIFROST_SEMANTIC_PACK_CACHE_ROOT=/path/to/cache
export BIFROST_OPEN_SEMANTIC_PACK_BUNDLE=/path/to/bifrost-semantic-packs
export BIFROST_POLICY_PACK_ROOT=/path/to/extracted-rules/rules
bifrost --root /path/to/project --mcp 'symbol|extended'

When both BIFROST_OPEN_SEMANTIC_PACK_BUNDLE and BIFROST_POLICY_PACK_ROOT are unset, the bifrost binary supplies its verified selection before the existing native and policy installers run. Setting either variable supplies host-owned content and disables automatic acquisition. The native bundle replaces embedded semantic registration; it is not merged with it. An empty value, malformed bundle, or bundle that installs no compatible pack makes activation fail. A policy report then carries pack-activation-failed, and the run exits 2. A malformed policy catalog also fails explicitly. Policy manifests and authored hashes are checked by the same catalog loader used for embedded policies. Runtime activation still requires the exact modeled dependency artifacts and workspace evidence; installation alone does not establish applicability or complete coverage.

Use .bifrost/packs.json to control ecosystems and reviewed packs, as described above. Empty ecosystems disables dependency discovery. Generated-production reuse remains keyed to the exact artifact digest and producer identity; a Linux production cannot be reused for a different toolchain or operating-system artifact merely because its pack ID matches.

Open content is acquired by the bifrost executable before runtime pack activation, using the optional download feature in brokk-bifrost-semantic-packs. Analysis, generic catalog activation, and local dependency generation remain network-free. Local generation remains available for exact dependency artifacts missing from the catalog.

Policy reports expose whether dependency activation was default, configured, or disabled, together with the selected, missing, incompatible, version-mismatched, rejected, or otherwise incomplete decisions. Activation failures and incomplete runtime state remain typed and attributable; they do not become a clean negative.

Every language whose discovery ran reports the standard-library pack it expects, even when nothing selects it: bifrost.python-stdlib, bifrost.php-builtin, bifrost.rust-stdlib, and bifrost.typescript-stdlib (which also explains that no pack declares a JavaScript standard library). The reason begins with the language. A workspace that declares no toolchain version gets a rejected decision naming the files Bifrost read. A declared toolchain with no installed pack gets a missing decision naming the Bifrost-packs install route. Each Node built-in module that a JavaScript or TypeScript file imports and no active pack declares, such as fs, gets a missing decision named node:<module>. Online and offline runs record the same decisions, because none of them involves the network.

The bifrost-semantic-pack binary uses the production source parser, schema, compiler, catalog, and activation resolver. Build it with the release-tooling feature:

cargo run -p brokk-bifrost-semantic-packs --features release-tooling \
--bin bifrost-semantic-pack -- validate model.yaml
cargo run -p brokk-bifrost-semantic-packs --features release-tooling \
--bin bifrost-semantic-pack -- lint model.yaml --format json
cargo run -p brokk-bifrost-semantic-packs --features release-tooling \
--bin bifrost-semantic-pack -- compile model.yaml compiled-model

validate checks the exact production schema and compiler. lint also reports duplicate IDs, unreachable or shadowed rules, ambiguous selectors, unused captures, broad wildcard selectors, and conflicting emissions. Some duplicate IDs and unknown capture references are compiler errors. The lint report keeps those production diagnostics.

compile writes the canonical manifest.json and exact shard bytes. A second run accepts identical files. It rejects an existing file with different bytes. This behavior prevents an authoring command from replacing reviewed output.

csmi-check FILE validates a CSMI semantic document or pack manifest and reports structural, semantic, integrity, and per-profile results. The binary also has release commands (generate, merge, verify, install) and summary-conversion commands. The command reference lists every command, its arguments, its JSON report format, and its exit status.

Human output is the default. --format json returns a stable versioned report. Status 0 means that the result is complete and has no finding. Status 1 means that source or conformance findings exist. Status 2 means that arguments or inputs are incompatible, or that a bounded operation is incomplete.

The smallest useful generator rule has one exact structured trigger, one required capture, and one typed emission:

payload:
kind: generator_rules
rules:
- id: rule.builder
trigger:
kind: annotation
name: com.acme.GenerateBuilder
captures:
- name: owner_id
binding:
source:
kind: enclosing_declaration
projection: stable_id
value_kind: stable_id
cardinality: one
emissions:
- kind: declaration
id:
op: concat
values:
- op: capture
name: owner_id
- op: literal
value: .builder
name:
op: literal
value: builder
declaration:
kind: member
owner:
op: capture
name: owner_id
member_kind: method
visibility: public

The complete declaration example below shows a dependency-qualified rule. Its package, version, target, configuration, and toolchain constraints must match one complete activation-evidence row.

list CATALOG shows installed packs, source attribution, and persisted catalog activation references. Add an activation JSON file to resolve active packs and show matched evidence, provenance, shadowing, incompatibility, and reasons:

{
"schema_version": 1,
"bifrost_version": "0.8.21",
"evidence": [
{
"language": "java",
"ecosystem": "maven",
"package": { "name": "com.acme:widget", "version": "1.5.0" },
"toolchain": { "name": "jdk", "version": "17.0.1" },
"target": "jvm",
"configuration": "release"
}
]
}
bifrost-semantic-pack list /path/to/catalog activation.json --format json

The activation file can contain controls. Each control has scope (user or workspace), action (enable or disable), pack_id, and optional version or manifest_digest fields. A control cannot bypass compatibility.

A repository can opt in to direct YAML or JSON files at this path:

.bifrost/semantic-models/

Run bifrost-semantic-pack workspace-check WORKSPACE during review. Discovery does not recurse. It reads files with the extension .json, .yaml, or .yml and ignores other files without a diagnostic. It rejects symbolic links, non-files, and files outside the canonical workspace. It allows at most 1,024 directory entries (limit.workspace_files) and 64 MiB per file (limit.source_bytes). Each result contains the exact source SHA-256 and compiled semantic digest.

Discovery is not ambient activation. A host calls register_workspace_semantic_models, which discovers each file, compiles it, and registers it as an EphemeralWorkspace session pack. The host then puts the returned evidence into its activation request and supplies an explicit workspace enable control for any pack that requires review. The production runtime then applies normal source precedence. Workspace sources outrank installed and shipped sources. Exact authored source or artifact declarations still outrank model-only overlay facts. workspace_semantic_models_not_active proves what actually activated, so a registered model can never go missing without the host noticing.

These hosts run the reviewed workspace route:

HostWhen it runsOn failure
A CLI policy run without --sources: bifrost --policy, --policy-file, and bifrost scanevery policy run, including the diff base, when the directory existsa workspace-model-load-failed report diagnostic and exit 2
A SearchToolsService session: the MCP server, one-shot --tool and --query-file runs, a policy run with --sources, and the Rust and Python librarieswhen the session activates packs, with BIFROST_WORKSPACE_SEMANTIC_MODELS=on (also 1 or enabled)the session records a failed activation; a policy run in that session reports pack-activation-failed and exits 2

BIFROST_WORKSPACE_SEMANTIC_MODELS is off by default. It also accepts off, 0, and disabled; any other value is an error.

A pack that declares safety.review_required stays inert on every host until an enable control names its pack id. Both routes read that list from .bifrost/packs.json. A policy report shows an inert pack as a workspace-model-inert warning.

Procedure-summary targets use the declaration identity that Bifrost publishes; they are not source-text search patterns. has_receiver is the normalized semantic receiver availability at the call boundary, not simply whether the syntax has an AST object. For Java, a fully-qualified or import-qualified static type qualifier such as URLDecoder.decode(...) is not an actual receiver, so a summary authors "has_receiver": false; the engine normalizes the proven type reference to that receiverless key. An instance transform such as s.trim() uses "has_receiver": true and cannot cross-bind to a static summary. For Rust, target.path is the artifact-relative path and target.symbol must be the declaration row’s exact dot-qualified fq_name, with the authored has_receiver flag and parameter_count matching that declaration’s structured callable signature.

target.symbol has two forms, and they differ in what supplies the procedure’s owner. A qualified symbol names its own owner: Acme.run at path: "src/acme.ts" targets the member run of Acme, and the path is provenance rather than part of the identity. A bare symbol targets a module-level declaration, which has no owner in its name at all; the module that target.path names is its owner. So run at path: "src/run.ts" targets the top-level function run of the module src/run, the path with its extension removed. Author the bare form for a top-level function in a language that qualifies nothing by package, such as JavaScript, TypeScript, or Ruby. Do not author a bare symbol for a member that has an owner: a qualified symbol and a bare symbol name different declarations.

Set variadic: true when a target declares a rest formal, and count every declared formal, including rest formals, in parameter_count. Schema fourteen formals identify a positional rest (*args), a keyword rest (**kwargs), or one rest slot that accepts both domains. A callable may declare one positional and one keyword rest formal. Each direct extra positional or unmatched named actual binds to the corresponding rest formal; an empty rest collection is valid. Calls with *values or **options spreads and unmatched keywords without a keyword-rest formal remain incomplete. Older unstructured variadic summaries retain their one-final-formal rule. Fixed-prefix claims, such as Testify’s refinement of err before msgAndArgs, apply at every accepted actual arity. A summary still cannot describe flow from or a refinement of a rest formal itself: authoring validation rejects claims that reference a variadic-tail ordinal.

For example, a free function declared as parse_ruby_tree in crates/bifrost-ruby/src/declarations.rs is authored as crates.bifrost-ruby.src.declarations.parse_ruby_tree with "has_receiver": false. Bifrost reports per-summary match counts in the pack activation review; an active reviewed workspace summary that matches zero procedures emits a workspace-model-inert warning naming the model, summary, and symbol.

Workspace files are data only. The loader uses the normal safe YAML or JSON parser. It does not load arbitrary code, follow links, execute a generator, download content, or read outside the workspace trust boundary. A content edit changes its review hash.

Analyzer hosts can use these public library functions:

  • explain_semantic_model_site identifies the pack, rule, source, activation evidence, captures, typed emissions, shadowing, and first failed predicate.
  • preview_semantic_model_emissions shows declarations, relationships, and aliases for one active rule and an explicit capture map.
  • scan_unmapped_semantic_model_sites scans normalized AST facts within file, node, and result limits. The caller supplies reviewed structured selectors and labels each family as model_eligible_generator or inspectable_source_macro.
  • run_semantic_model_conformance checks a golden fixture (fixture format version 1) against the production overlay and matcher.

The scan classification is explicit. A call-shaped AST node does not prove that generated output is safe to model. The scan never uses regular expressions, source-text search, generator execution, or an implicit download. An exhausted limit marks the report incomplete.

Conformance fixtures can assert symbols, owners, signatures, hierarchy, relationships, forward definitions, inverse usages, authored anchors or portable model URIs, provenance, completeness, and positive or negative matches. Keep fixtures under review with the model. Run them after every model change. The report uses bifrost_semantic_model_conformance/v1 and returns explicit missing expectations.

For a miss, first inspect first_failed_predicate. Then inspect activation evidence and shadowing. A trigger mismatch means that the normalized node or exact trigger name did not match. An unbound capture means that the structured fact did not supply one required value. A conflict means that equal-precedence rules failed closed, so production emitted neither rule.

Every source pack must contain a schema_version this build supports, from 2 through 17. The field is mandatory: omitted, zero, and unsupported versions fail instead of falling back. Every object rejects unknown fields, and every variant is explicitly tagged. A schema addition takes a new version number and a new checked-in schema rather than silently widening a number that already shipped.

Producers write 17. Schema seventeen adds runtime_bases to Python class facts. An absent claim makes no assertion; an explicit empty list claims a runtime root. A populated list claims the exhaustive ordered direct runtime bases, using non-nullable, non-parameterized named or declared class references. It is separate from typing inheritance and member-inventory completeness. The current Python CSMI export profile rejects these claims rather than losing them. Schema sixteen adds exact_return_classes to callable declarations. This author-written set lists every runtime class a callable can return; it does not follow from the return annotation or callable-shape completeness. Without the claim, the result remains an open class bound. Schema fifteen added typed module-value facts and request roles on callable parameters. Reading a set of versions rather than one exact number is what lets an installed older pack or release asset keep loading across the bump and be regenerated on its producer’s normal cadence. A field or variant a version introduces is rejected in a pack that declares an earlier version, so a pack that an older reader accepts by its number is one that older reader can also parse: ambient_use on a schema_version: 2 pack is a validation error, not an additive field. Schema eleven added structured procedure-formal names, passing modes, and default-presence facts. These let a summary bind supplied positional or keyword actuals while leaving only declared-default formals unbound. Schema eleven also introduced reviewed channel-close and returned-callable invocation effects.

Schema twelve remains supported and carries the reviewed concurrency effects, using channel identities declared by the model. Unresolved context or callable identities remain open. Schema thirteen combines both histories’ structured formals and reviewed effect fields. Schema fourteen adds a directional kind to structured rest formals, so direct extra positional or unmatched named actuals bind exactly; call-site spreads remain incomplete.

Version ten adds native callable-value facts and scoped coverage to authored shards. A callable value names the exact declaration and invocation shape that produces it, while coverage records which source locations were reviewed.

Version nine adds kind: unknown for a parameter or result type port whose type is unavailable. It preserves the fact that the port exists while leaving its type unresolved. This differs from an absent result port and from an incomplete signature or body; none of those absences supplies an unknown type for a known port.

The machine-readable contract is schemas/semantic-model-pack-v17.schema.json. It is generated from AuthoredSemanticModelPack; a repository test requires the checked-in bytes to match the Rust-derived schema exactly. The version-two through version-eight schemas stay checked in as frozen descriptions.

The version table lists each version’s additions and the validation code for each gate.

Version eight removes compatibility.bifrost. Version-seven and older packs retain the required engine range; version-eight and newer packs omit it. The producer version still names the tool that wrote the pack, but it does not gate activation. When a catalog is upgraded, existing packs keep their bytes and digests. Toolchain requirements, artifact identity, selectors, and integrity checks continue to apply for every version.

Version seven adds no_concurrency_effects to procedure summaries. It is a reviewed claim that the procedure neither synchronizes with caller-visible memory nor retains it for concurrent work. Only a complete summary with no concurrency_effects may make it.

Version six adds callable_surface_complete to TypeFact. It is a positive inventory claim: every directly declared, externally visible callable and every direct hierarchy edge of the type is in the pack. It does not cover inherited members, fields, effects, or overload applicability.

Version five adds reviewed normal-result use obligations to procedure summaries. An obligation names one normal-result ordinal and either a pure transformation value or a fallible status with an explicit failure predicate. An omitted obligation makes no claim that discarding the result is safe or unsafe.

Version four adds the runtime_contracts companion for CSMI runtime-values 0.2. It retains the five fact families and their semantic-document envelope. Packs declaring an older schema cannot carry this companion. Importing a contract does not establish deployment identity, consumer review, or a native runtime load; those remain separate evidence requirements.

Version three: contextual declaration roles

Section titled “Version three: contextual declaration roles”

Version three adds one optional field, ambient_use, to TypeFact and MemberFact. It answers whether importing that declaration can consume it without the importing file spelling the imported name: not_ambient proves that every use spells the name, while implicit and given name a contextual role the compiler selects by type. Absence means the producer did not review the declaration, so a consumer must treat it as unknown; a Method, Property or Class kind is not negative evidence either, because Scala records an implicit val, def, class and object under exactly those kinds. Only not_ambient proves anything, which is what lets the unused-import derivation publish a Scala finding whose target is outside the workspace.

YAML is a presentation syntax, not a second data model. Loading permits one document and rejects duplicate keys, aliases, anchors, merge keys, includes, property expansion, legacy boolean spellings, excessive nesting, excessive events, and over-budget scalar or comment data. JSON and YAML both deserialize directly into types that reject unknown fields.

The envelope records a stable pack ID and semantic version, producer identity, language and ecosystem, toolchain compatibility (and an engine version range in versions 2 through 7), provenance, an SPDX license expression, completeness, safety metadata, and independently loadable shards. The envelope reference lists each field, whether it is required, and its allowed values.

Each shard has one or more activation selectors. A selector can identify a package, module, or declared toolchain using an exact name and optional SemVer constraint. It may narrow activation by target, configuration, or a lowercase SHA-256 artifact digest. The compiler derives sorted routing keys from these selectors and, for rule shards, their trigger kinds. The runtime uses the keys to avoid reading unrelated payloads, then strictly rechecks every populated selector field. Missing evidence never satisfies a constraint.

A version constraint states the compatible range the pack’s extracted API surface serves, not the one build it was extracted from. A pinned release spec must bound that range above, so a pack cannot answer for an API surface it was never extracted from; bifrost-semantic-pack generate rejects an open requirement such as >=21.0.0 in a spec. Authored packs are not subject to this check: validate accepts an open requirement. The exact build stays recorded as the pack version, the pinned artifact digests, and the upstream provenance revision. Because activation no longer implies that the workspace runs that exact build, the activation decision names both the build the pack carries and the requirement that admitted the workspace: a selected pack’s reason in packs.decisions reads pack version 21.0.8; matched toolchain jdk 21.0.0 against the pack requirement >=21.0.0, <22.0.0. A version outside the range is still rejected, with a reason naming the workspace version and the requirement.

Activation evidence is supplied as complete rows so a package, module, toolchain, target, configuration, and artifact digest from different resolved artifacts cannot be combined accidentally. Exact artifact evidence outranks versioned coordinates, which outrank named coordinates and language-only selectors. Ephemeral and durable workspace-produced sources outrank generated, installed, pre-shipped, and embedded sources in that order. A workspace control outranks a user control, but neither can bypass compatibility. Packs marked review_required need an explicit compatible enable. Equal-rank conflicting facts remain conflicts instead of becoming a last-write-wins answer. The discovery and precedence reference gives the full selection order and the activation status values.

The generation-scoped runtime owns every selected decoded shard and builds exact-key postings for type and member IDs and names, aliases, relation IDs and directions, and every generator trigger. Lookups do not read SQLite, pack files, or unrelated postings; the matcher has no wildcard fallback path. Work, index entries, working bytes, retained bytes, and explanations are bounded. Cancellation, corruption, stale generations, and exhausted budgets never publish a complete cached value.

SemanticPackCatalog stores durable packs beneath one caller-selected shared root. The caller chooses the root explicitly so a host can apply its own platform and configuration policy. Catalog metadata lives in a separately versioned SQLite database. Immutable shard bytes live once at objects/sha256/<first-two-hex>/<remaining-hex>, keyed by the SHA-256 of their exact stored representation. The manifest content digest identifies the complete pack; semantic and uncompressed-content digests retain their distinct artifact roles.

Installation validates the canonical manifest and every manifest-bound shard before publishing anything discoverable. Files are staged, synchronized, and atomically moved into the content-addressed tree. A single metadata transaction then publishes the complete pack, its normalized selectors, and its source. Installing identical bytes from several workspaces or sources reuses the same physical object while retaining every durable source attribution. Startup reconciliation removes bounded abandoned staging and unreferenced final files; it never promotes an orphan into an installed pack.

Candidate discovery narrows by language, ecosystem, and the populated package, module, toolchain, or artifact selector index without reading shard payloads. It then checks SemVer compatibility, target, configuration, and artifact identity from validated catalog metadata. Candidates are opaque catalog handles: callers can inspect their identity and source through accessors but cannot alter the descriptor or provenance used by verified loading. Loading rechecks the digest path, size, stored bytes, manifest envelope, and decoded shard. Missing or corrupt durable content becomes a safe miss and is quarantined in a writable catalog; a read-only catalog rejects durable mutations and suppresses repeated attempts only for that process.

Durable source kinds are installed, generated, pre-shipped, and workspace-produced. Embedded release resources and ephemeral-workspace packs use the same complete validation and selector path but remain in the catalog instance’s session memory, so they are never copied into durable storage. Persistent workspace active sets may reference only exact registered durable sources. In-memory workspaces may reference only exact session sources because they have no durable workspace identity that can own a cross-process activation. Their activation accounting is tied to the workspace store’s lifetime. Catalog active-set identity is a domain-separated digest over the full sorted manifest and source references, and durable activation rows protect selected objects across processes.

That catalog identity is deliberately distinct from the runtime active_model_set_hash. The runtime hash covers selected semantic shard digests, payload kinds, matcher precedence and activation evidence, extraction gaps, and the matcher representation version. It excludes equivalent source attribution and storage encoding. Analyzer-snapshot caches use a canonical activation-request key and retain only complete immutable runtimes. Before publication, the runtime rechecks source generations and coordinates selected catalog references with the workspace store. A failed or incomplete build preserves the previous active set.

Projection into synthetic analyzer declarations and model URIs

Section titled “Projection into synthetic analyzer declarations and model URIs”

A ready declaration runtime publishes one immutable overlay in the analyzer snapshot cache. The overlay owns typed type, member, hierarchy, and relation rows plus exact indexes by stable fact ID, qualified name, alias, and location. It is not part of the project filesystem: overlay records never appear in Project::all_files(), never become ProjectFile values, and never trigger file watching or source-cache operations.

The overlay also evaluates active generator-rule shards against the analyzer’s normalized structural facts. It evaluates language_construct, annotation, annotated_field, macro_invocation, and generator_invocation triggers, every capture source except resolved_owner, bounded template expressions, and typed declaration, relation, and alias emissions. A fully qualified annotation trigger matches the same qualified spelling. An unqualified annotation matches a qualified trigger only when exactly one non-wildcard import in scope binds it to that qualified name. resolved_owner and resolved_call triggers never match until the analyzer can supply their typed resolver evidence; Bifrost does not replace that missing evidence with source-text matching.

Every overlay declaration has one honest location. If the pack’s locator names a real declaration in the current analyzer snapshot and the exact symbol and path resolve, navigation uses that authored anchor and its real declaration range. Otherwise Bifrost creates a deterministic, percent-encoded bifrost-model://v1/<pack-semantic-digest>/<record-kind>/<stable-record-id> URI with a deterministic virtual range. A model URI is a portable identity and navigation target, not generated source. Source-reading tools render a bounded typed description explicitly labeled as modeled content.

A unique navigates_to relation on a model-only declaration is followed by definition and declaration navigation. Its target may be another unique modeled declaration or an exact authored symbol. Multiple or conflicting navigation targets fail closed, and an absent target is reported instead of falling back to the model declaration.

Symbol search, symbol locations, definition and modeled-usage results, CodeQuery declaration projections, and MCP and Python JSON preserve the overlay provenance. It includes the active-model hash, pack semantic digest and identity/version, producer and version, stable record ID, catalog source and activation explanation, origin, proof kind, completeness, and ambiguity. Model relations are reported as semantic facts and do not invent source snippets or call-site ranges.

Authored workspace declarations and exact navigable artifact declarations win over model-only locations. A unique model fact fills information that authored analysis does not have. Equivalent facts deduplicate; equal-rank differing facts remain ambiguous, and definition, usage, hierarchy, and query consumers must not select one. Catalog mutation generation and SQLite data-version are part of runtime cache identity, so an install, replacement, quarantine, or source removal cannot leave an old overlay attached to an unchanged analyzer snapshot and activation request.

Accounting reports deduplicated installed and active bytes, physical objects, logical and active shards, sources, lookup hits and misses, quarantined packs, and activation counts by durable or session source. Pins, durable sources, workspace activations, reader leases, and in-flight installation reservations protect content from collection. Explicit bounded garbage collection removes only old packs with none of those roots, rechecks each object under the catalog write boundary, and reports bytes only when a file was actually removed.

Bifrost can construct declaration packs directly from one caller-selected Java source JAR, Java class JAR, or .NET assembly. The producer API does not discover dependencies, infer package coordinates from filenames, download artifacts, solve classpaths, or install its result. Discovery remains an analyzer concern; production receives the exact path together with explicit pack, ecosystem, compatibility, activation, provenance, license, and safety metadata.

The producer reads and hashes the exact bytes once under a caller-controlled artifact limit. It copies the lowercase SHA-256 into every supplied activation selector and returns it beside the authored pack. Archive entry counts, per-entry and total uncompressed bytes, declaration records, signature depth, diagnostic count, diagnostic text, and diagnostic locations are bounded. Invalid input that cannot be identified produces no pack and, when the caller’s diagnostic budget permits, a bounded error diagnostic. Unsupported metadata or an exhausted extraction limit can instead produce a useful partial pack with stable diagnostic codes and a suppressed-diagnostic count.

Java source declarations are read from tree-sitter syntax. Java class descriptors and generic Signature attributes use a bounded grammar parser; C# types are read structurally from PE/CLI metadata. Producers emit public and protected API types and members after applying enclosing-type visibility. Java package-private declarations remain available to the legacy same-package resolver but are not exported as reusable public API facts. Dependency entries and assemblies remain external and are never added to Project::all_files().

Declaration identity deliberately excludes origin. Equivalent Java source and class declarations receive the same IDs, as do equivalent C# declarations from another copy of the same semantic API. Source paths, JAR entries, assembly metadata tokens, parameter names, and artifact digests do not participate in those IDs. They do participate in locators, activation, or compiled pack bytes, so a source pack and binary pack can share declaration IDs while retaining different pack and shard digests. Member identity includes owner, kind, name, generic arity, ordered parameter types, and return type. Including return type preserves distinct CLI metadata members such as conversion operators even though ordinary Java and C# source methods cannot overload only by return type.

Binary formats do not guarantee parameter names. signature.parameters[].name is therefore optional: producers retain a source or binary name when it is available and omit it otherwise rather than inventing arg0-style data. signature.parameters[].passing_mode records whether a formal accepts positional_only, positional_or_named, or named_only arguments. The field defaults to positional_or_named; Python stub producers derive the other modes from the structured /, *, *args, and **kwargs parameter regions so model applicability cannot accept a call the declared API rejects. Generic parameter names are retained from Java Signature or CLI GenericParam metadata when present. Unsupported generic shapes make the result partial instead of being flattened to a misleading string.

Hosts can connect dependency discovery to the shared catalog without giving the analysis library ownership of a global path. Open a SemanticPackCatalog at the host-selected root, resolve exact records with resolve_jvm_semantic_pack_dependencies or resolve_csharp_semantic_pack_dependencies. Go hosts opt in through AnalyzerConfig::go.dependency_discovery, call resolve_go_semantic_pack_dependencies, and use GoDependencyPackAdapter. Pass the returned DependencyDiscoveryOutcome to prepare_discovered_dependency_semantic_packs with the matching adapter. The discovery outcome carries bounded diagnostics, completeness, cancellation, and input/resolution counts; an unresolved coordinate or malformed dependency manifest therefore cannot collapse into a successful empty dependency set. Preparation never downloads packages, scans an entire package cache, or implicitly builds a project. Maven and Gradle build-tool discovery remains explicitly offline and opt-in.

Each generated-production key covers the ordered artifact roles and exact byte digests, producer and adapter versions, semantic-pack schema version, activation evidence, normalized ecosystem provenance, and every producer or compiler limit that can affect output. Paths and mtimes are excluded. Artifact locators stored by local-dependency adapters are content-addressed rather than derived from a local filename. A second workspace using identical bytes and evidence through the same catalog therefore reuses the verified manifest without invoking the producer. A byte, coordinate, target, configuration, asset-role, adapter, producer, or schema change creates a different production. Installation binds the production key, generated source, and verified manifest in one catalog transaction; corrupt bindings are safe misses, and garbage collection removes bindings with their unreferenced manifests after the configured age. Generated source attribution is cache metadata, not a permanent GC root; pins, active sets, and leases protect generated packs that remain in use. Lookup verifies the manifest and every referenced shard object before reporting reuse.

JVM records retain exact Maven coordinates and distinguish Maven reports, Gradle reports, Maven repositories, Gradle coordinate-cache directories, and explicit paths. A class JAR and optional source JAR form one production. Source facts and locators win for shared stable declaration IDs, binary-only facts are added, and incompatible facts make coverage partial with a bounded diagnostic. The legacy resolver still retains package-private declarations that are not exported as reusable public API facts. It consumes the same bounded dependency discovery result, while retaining its compatibility projection for those analyzer-only declarations.

.NET records retain NuGet package/version, target framework, configuration, reference/compile/runtime role, and project-reference provenance. Reference assets outrank compile and runtime duplicates for the same semantic assembly; different targets and project-output configurations remain separate evidence. Explicit assembly paths do not invent package coordinates.

Rust records use a passive, host-supplied evidence bundle. The bundle contains Cargo metadata format-version-1 JSON, its Cargo.lock, the selected target and configuration, selected workspace targets, exact per-package feature lists, and an explicit rustdoc JSON artifact for every dependency API to index. resolve_rust_semantic_pack_dependencies validates that registry, git, and path packages are reachable from the selected targets and agree on package version, source, checksum, crate name, target triple, rustdoc format, and artifact binding. Dependency renames remain provenance; package and crate identity do not change with the local binding name.

Bifrost does not run Cargo or rustdoc to create this evidence. It does not scan Cargo caches or target directories, download crates, compile dependencies, run build scripts, or load procedural macros. A host may generate rustdoc JSON in a separately controlled build step and pass the resulting paths through RustAnalyzerConfig. Bifrost then reads only those paths. The decoder is pinned to one exact rustdoc-types format; missing, inconsistent, or unsupported artifacts produce explicit incomplete coverage. Public procedural-macro names may appear in a pack, but macro code is never loaded or executed. Exact nightly toolchain strings are retained in production provenance and cache identity rather than treated as semantic-version compatibility coordinates.

Go discovery asks only the configured go executable for machine-readable go env and go list metadata. The child process has its ambient Go configuration cleared, uses GOTOOLCHAIN=local, GOPROXY=off, GOSUMDB=off, GOENV=off, and CGO_ENABLED=0, and receives the configured GOOS, GOARCH, and build tags. When non-test files are excluded, a second metadata-only go list with cgo enabled identifies cgo surfaces that the disabled view would otherwise classify only as ignored. It never builds, tests, runs, or generates a package and it never downloads a module or toolchain. The selected standard library, module-cache, local replacement, workspace, and vendor files become exact source-set inputs; normalized relative paths and retained bytes determine their identity, not absolute cache locations or mtimes.

Generated Go packs contain exported packages, types, aliases, functions, variables, constants, methods, fields, structured signatures, type parameters and constraints, underlying types, embeddings, receiver forms, and promoted members. Exact import paths remain package identity even when the declared package name differs from the path’s last segment. Non-exported carrier types may be retained to derive a public promoted surface, but overlay search and navigation do not expose them. Activated facts participate in definition, hover, signature, hierarchy, symbol, and whole-workspace reference paths while dependency and GOROOT files remain outside Project::all_files().

Coverage is explicitly partial when selected packages report errors, cgo or generated source participates in the selected build, build constraints are malformed or otherwise rejected by the configured Go toolchain, source is malformed, or local artifacts, time, cancellation, or a configured bound stop discovery. Files that the toolchain excludes for the exact target and tags do not enter the pack and do not by themselves make that configured surface partial. Cgo execution, compiler-equivalent type checking, SSA/body indexing, go generate, and implicit network resolution are not provided. Go internal package access is checked from canonical import paths, and authored workspace declarations take precedence over otherwise matching pack facts.

Python environment records are intentionally more explicit. A host supplies a declared interpreter implementation, version, platform, standard-library root, optional bundled-stub roots, and selected distribution roots; Bifrost never discovers sys.path, starts Python, imports a module, executes package setup, or contacts a package index. It reads only .pyi and safe .py files plus static .dist-info/.egg-info metadata. Precedence is deterministic: bundled stubs, stub artifacts, inline py.typed source, then safe implementation source. Dynamic exports, malformed files, and missing static surfaces remain partial coverage with diagnostics rather than invented declarations. Hosts activate the result explicitly with WorkspaceAnalyzer::activate_python_environment_packs; dependency files stay outside Project::all_files(), and external navigation uses bifrost-model: locations rather than synthetic workspace files.

A stub declares part of its surface inside a sys.version_info or sys.platform block. The producer never evaluates such a block. It records the block’s condition on each declaration the block encloses, as an inclusive minimum toolchain version, an exclusive maximum, and required or excluded activation targets. A condition it cannot express is recorded as an uninterpreted guard, which keeps the declaration and states that the pack read less than the whole condition. Activation then drops a declaration only when the pinned toolchain version or target provably fails a recorded constraint, so an unknown coordinate and an uninterpreted guard both keep the declaration. The declared platform is therefore the interpreter’s own sys.platform value, the same vocabulary a stub’s platform guard names.

A library host supplies this environment in AnalyzerConfig::python.environment (PythonEnvironmentConfig): the implementation, version, platform, standard_library_root, bundled_stub_roots, and distribution_roots. Separately, .bifrost/packs.json can bind a source tree to installed wheels through python_runtime_environments, so Bifrost can verify the installed files against the original wheel archives without running Python. The reference describes that field. The separately distributed bifrost-lsp server documents its own initialization options.

Ruby records likewise use passive, host-supplied evidence. A RubyDependencyApiEvidence row identifies one Gemfile.lock by path and SHA-256, the exact Ruby version and platform, explicit approved archive roots, and each selected gem’s name, version, source, optional checksum, and .gem archive path. resolve_ruby_semantic_pack_dependencies reads only those named files. It does not run Ruby, Bundler, RubyGems, Sorbet, Steep, extension builds, gem hooks, or generators; it does not scan gem caches or access the network.

The Ruby adapter reads a .gem as bounded nested tar and gzip streams without extracting it. It projects RBS through the typed ruby-rbs parser and parses RBI and ordinary Ruby declarations with tree-sitter. RBS takes precedence over equivalent RBI, which takes precedence over ordinary source. Reopened scopes and overloads are retained, while contradictory typed declarations make the pack partial and remain visible as conflicts. Classes, modules, instance and singleton methods, attributes, aliases, structured signatures, and ordered prepend, include, and extend relations are supported. Unsupported dynamic metaprogramming produces bounded partial diagnostics rather than speculative facts.

An embedded host supplies the evidence explicitly:

use std::path::PathBuf;
use brokk_bifrost::{
AnalyzerConfig, RubyAnalyzerConfig, RubyDependencyApiEvidence,
RubyGemApiArtifact,
};
let config = AnalyzerConfig {
ruby: RubyAnalyzerConfig {
dependency_api_evidence: vec![RubyDependencyApiEvidence {
lockfile_path: PathBuf::from("Gemfile.lock"),
lockfile_sha256: "<lowercase-sha256>".into(),
ruby_version: "3.4.1".into(),
platform: "ruby".into(),
approved_archive_roots: vec![PathBuf::from("/controlled/gems")],
gems: vec![RubyGemApiArtifact {
name: "rack".into(),
version: "3.2.1".into(),
source: "https://rubygems.org/".into(),
checksum: Some("<lowercase-sha256>".into()),
gem_archive_path: PathBuf::from("/controlled/gems/rack-3.2.1.gem"),
}],
}],
},
..AnalyzerConfig::default()
};

Relative lockfile and archive paths are resolved against the project root; canonical archive paths must remain under the project root or an explicit approved root. Hosts then pass the discovery outcome to prepare_discovered_dependency_semantic_packs with RubyDependencyPackAdapter, compose its exact evidence into the activation request, and activate it through the normal semantic-model runtime. Archive members receive digest-qualified logical locations and never become project files.

Three more adapters read only local files. The npm adapter reads package-lock.json and npm-shrinkwrap.json lockfiles and the manifests of locally installed packages to find exact declaration entry points; it does not add dependency files to the workspace. The Composer adapter reads composer.lock, vendor/composer/installed.json, and installed package trees under approved vendor roots, with at most 64 autoload rules per package; it never runs Composer, a Composer script, or a plugin. The C++ adapter reads compile_commands.json and builds packs from the headers reachable from include roots outside the workspace. The Python dependency resolver reads the configured environment described above, or, when none is configured, the workspace’s declared toolchain to select a standard-library pack. The ecosystem table maps each .bifrost/packs.json label to its languages, input files, and bounds.

Preparation is bounded by dependency, artifact, total-byte, producer, compiler, and diagnostic limits. It checks cancellation between file chunks, JAR entries or source files, CLI metadata batches, and every lookup/production/compile/install boundary. Missing, unreadable, malformed, unsupported, cancelled, or over-budget inputs remain explicit partial coverage; they never become an authoritative empty result. The returned profile separates artifacts and bytes read from reused and generated packs. Partial generated packs are reused because identical inputs to a deterministic producer cannot improve their coverage. Reuse reports an actionable partial coverage diagnostic instead of rerunning the producer.

DependencyPackPreparationOutcome::compose_activation_request merges exact successful evidence into a host-owned activation request. It returns no request after cancellation or wholly unavailable partial preparation, so a host cannot accidentally replace a previously complete workspace active set with authoritative empty dependency coverage. Preparation itself never publishes an analyzer overlay or workspace active set.

A declaration shard contains typed records rather than arbitrary maps: types, members, structured signatures and type references, explicit member ownership, typed hierarchy edges, aliases, extension surfaces, navigation/reference relations, and typed source or artifact locators. Type and member facts carry visibility and relevant modifiers. Language-facing names retain ordinary language spelling such as TValue, _value, or getURL; only pack and fact identities use Bifrost’s lowercase stable-ID grammar. The declaration facts reference lists every field of TypeFact, MemberFact, Signature, Parameter, and TypeRef, with its default and the meaning of its absence. The example below is a complete, valid pack.

A curated declaration pack may remain globally partial while one reviewed fixed-arity member sets callable_family_complete: true. The claim covers only that member’s exact structural lookup family: language, owner, member name, receiver form, and arity. It does not prove the owner’s whole member surface or the pack complete. A second declaration in the same family remains ambiguous, and authoring validation rejects the claim on variadic members until their multi-arity completeness can be represented explicitly. Omit the field (its default is false) whenever that exact family has not been exhaustively reviewed.

schema_version: 2
pack_id: acme.widget
version: "1.2.0"
producer:
name: artifact-scanner
version: "2.0.0"
language: java
ecosystem: maven
compatibility:
bifrost: ">=0.8.0, <1.0.0"
toolchains:
- name: jdk
requirement: ">=17.0.0"
provenance:
source: "https://repo.example/acme/widget-1.2.0.jar"
revision: "sha256:example"
license: Apache-2.0
completeness: complete
safety:
generated_code_only: false
review_required: false
shards:
- id: declarations.widget
activation:
- package:
name: com.acme:widget
version: ">=1.0.0, <2.0.0"
targets: [jvm]
configurations: [release]
payload:
kind: declaration_facts
types:
- id: type.widget
name: com.acme.Widget
type_kind: class
visibility: public
type_parameters: [t]
hierarchy:
- hierarchy_kind: extends
target:
kind: named
name: java.lang.Object
aliases: [com.acme.LegacyWidget]
extension_surfaces: [com.acme.WidgetExtensions]
locator:
kind: artifact
path: com/acme/Widget.class
symbol: com.acme.Widget
members:
- id: member.widget.create
owner: type.widget
name: create
member_kind: method
visibility: public
signature:
parameters:
- name: input
type:
kind: named
name: java.lang.String
returns:
kind: named
name: com.acme.Widget
aliases: []
locator:
kind: artifact
path: com/acme/Widget.class
symbol: create(java.lang.String)
relations:
- id: relation.widget.navigation
relation_kind: navigates_to
from: member.widget.create
to: type.widget

Curated Go behavior packs activate alongside declaration companion packs when an exact package function or concrete result type must be selected at a call site. The companions are deliberately partial package surfaces: each declaration is a positive identity and signature fact, while an unlisted package member remains unknown rather than proved absent. Separate companions ship for os, errors, log, net, net/url, net/http, bytes, encoding/pem, crypto/x509, sync/atomic, the concurrency summaries, and Testify require and assert; the testing pack already carries its partial concrete-receiver declarations. Keeping a partial declaration surface out of a complete behavior pack also preserves the envelope invariant that a partial pack cannot claim a complete summary. The declarations-only path/filepath pack publishes the module plus the exact variadic Join(...string) string signature, allowing a containing modeled call to distinguish its single result from Go’s multi-result argument expansion without attaching a behavior summary to Join.

A generator shard declares a typed trigger, trigger-relative capture bindings, and typed declaration, alias, or relation emissions. Capture sources identify the matched node, the enclosing declaration, the owning type, the owned fields (all, mutable only, or uninitialized final only), the resolved owner, one argument, an argument suffix, or a named annotation argument. A projection then requests a name, stable ID, type, text, or path. The compiler checks that each source is available for its trigger and that its projection, declared value kind, and cardinality agree. Each source has one required cardinality.

A many capture produces one emission for each value; several many captures in one rule must produce the same number of values. A group capture keeps its values together in one emission, where repeated_parameters renders once per value. A template cannot use an optional capture. Language-name positions accept identifier or stable-ID captures, type positions accept only type captures, and stable-ID positions accept only stable-ID captures. Unknown captures and stable-ID templates with unsafe boundaries or case transforms fail compilation. The generator rules reference lists every trigger, capture source, template operation, and emission.

schema_version: 2
pack_id: acme.builders
version: "1.0.0"
producer:
name: policy-author
version: "1.0.0"
language: java
ecosystem: maven
compatibility:
bifrost: ">=0.8.0, <1.0.0"
provenance:
source: "https://docs.example/acme-builders"
license: MIT
completeness: partial
safety:
generated_code_only: true
review_required: true
shards:
- id: generators.builders
activation:
- package:
name: com.acme:builders
version: ">=1.0.0, <2.0.0"
payload:
kind: generator_rules
rules:
- id: rule.builder
trigger:
kind: annotation
name: com.acme.GenerateBuilder
captures:
- name: owner_id
binding:
source:
kind: enclosing_declaration
projection: stable_id
value_kind: stable_id
cardinality: one
- name: entity
binding:
source:
kind: enclosing_declaration
projection: name
value_kind: identifier
cardinality: one
- name: entity_type
binding:
source:
kind: enclosing_declaration
projection: type
value_kind: type
cardinality: one
emissions:
- kind: declaration
id:
op: concat
values:
- op: capture
name: owner_id
- op: literal
value: .builder
name:
op: transform
transform: pascal_case
value:
op: capture
name: entity
declaration:
kind: member
owner:
op: capture
name: owner_id
member_kind: method
visibility: public
signature:
returns:
kind: capture
name: entity_type

A procedure-summary shard describes externally reviewed behavior without activating it. Each record has a stable ID and a structured target consisting of a canonical artifact-relative path, an exact symbol, normalized semantic receiver availability, and parameter count. The compiler derives a pack-scoped model ID, a summary contract version, and a content digest for every record; the defensive decoder recomputes and verifies those fields.

For Java, has_receiver distinguishes an actual value receiver from a type qualifier. Fully-qualified and import-qualified static calls such as URLDecoder.decode(...) bind with false, after structured resolution proves that the qualifier names the target owner. Instance transforms bind with true; unresolved, shadowed, ambiguous, or mismatched receivers remain inconclusive rather than being guessed. This field remains a boolean and its canonical key is unchanged.

Transfers connect a receiver or zero-based parameter to a normal return, an indexed normal result of a multi-result procedure, the receiver, an exceptional return, a declared capture, or a declared heap location. Each transfer carries an explicit normal or exceptional exit kind, matching the reusable-summary contract even when an exceptional exit writes a heap or capture location. Effects represent allocation, calls to another summary in the same pack, escapes, unknown calls, unknown-call boundaries, explicitly ambiguous call sets, and sanitizers that remove named taint labels on a declared transfer. Inputs must exist on the target, outputs must reference a location of the right kind, call targets must exist, and all collections have fixed validation budgets. Duplicate targets and duplicate IDs are rejected across shards.

normal_continuation_absent: true is an optional reviewed control claim that the exact procedure never returns normally. Omission or false makes no claim. The flag can be the only substantive content in a summary, and a partial summary may state it because completeness of other behavior is independent of this explicit control fact. A summary carrying the flag cannot also contain a normal transfer, an allocation or sanitizer whose output is a normal return, a result contract, or a normal-return refinement. Exceptional transfers and effects that occur before termination remain valid. normal_result_count is signature metadata rather than a normal-return claim, so it may also remain on a non-returning summary. At activation time, matching summaries must agree on the flag; an empty or conflicting match never proves that normal control is absent.

declared_effects is a separate, optional list of namespaced effect identifiers the reviewed pack attributes to the exact procedure, such as acme.network_io. Unlike effects, a declared effect names no input, output, or callee and never joins the transfer graph; it lets a policy ask whether a call performs an effect without new analyzer code. Each entry carries:

  • id: a stable identifier that must contain at least one ., so two vendors can ship packs without colliding on a bare word like write.
  • timing: immediate (before the call returns), deferred (scheduled by the call), or unknown (the pack asserts the effect but not when it happens).
  • certainty: definite (every execution performs it) or possible (some execution may).

A summary may declare at most 64 effects, and one summary may not repeat an id. A partial summary must carry substantive content: a transfer, a complete transfer partition, an effect, a concurrency effect, a precondition review, a result contract, a result-use obligation, a conditional refinement or write, a normal-return refinement, a class decorator identity, or normal_continuation_absent. Declared effects alone do not satisfy that rule. A complete summary may be empty. Two activated summaries that match the same procedure and disagree about their declared effects resolve to a conflict and are not used. Omitting the field changes no digest, so a pack that declares no effects keeps the compiled bytes and the digests it had before the field existed.

A procedure summary can publish optional call-entry preconditions over its receiver and parameters. The field is three-state: omission means entry requirements were not reviewed, an empty list is a reviewed claim that there are none, and a populated list states the exact predicates required at the call. Exact call-argument analysis uses these claims only when every possible runtime target is covered and agrees. This is distinct from normal-return refinements, which establish a predicate after a call returns.

A result contract relates one indexed normal result to a predicate over a different indexed result, such as a resource whose validity requires its paired error to be null. The finite predicate vocabulary is null, non_null, true, and false; the Boolean values let a reviewed (value, ok)-style API state which exact outcome validates the protected result. They do not evaluate arbitrary expressions. A member contract can then describe one operation on that result by exact member name and parameter count. Its own optional preconditions field has the same three states:

  • omitted means the operation’s input requirements were not reviewed;
  • an empty list means the operation was reviewed and requires no input predicate;
  • a populated list names each receiver or parameter predicate the operation requires.

These states are not interchangeable. Omitting knowledge must not create a finding or a clean negative, while a reviewed empty list is positive evidence that the operation is applicable without first establishing the result’s success predicate. Preconditions reuse the summary’s typed inputs and result predicate vocabulary, so a pack cannot invent an unbound receiver, parameter, or textual condition.

Completeness is explicit at both levels. A partial record remains partial after compilation and decoding; a partial pack cannot claim a complete record. Completeness is evidence metadata only and does not enable matching or flow application.

Summaries can also carry concurrency effects, transfer partitions, conditional result refinements and indirect writes, normal-return type refinements, covers_overrides, ordinary_heap_unchanged, and a class decorator identity. The procedure summaries reference lists every field, its minimum schema version, and its validation rules.

payload:
kind: procedure_summaries
summaries:
- id: summary.helper
target:
path: com/acme/Flows.class
symbol: helper(java.lang.String)
has_receiver: true
parameter_count: 1
completeness: partial
locations:
- id: location.receiver-field
location_kind: heap
transfers:
- input:
kind: parameter
ordinal: 0
exit_kind: normal
output:
kind: normal_return
effects:
- kind: allocation
event: event.helper.allocate
output:
kind: heap
location: location.receiver-field
- kind: unknown_call_boundary
event: event.helper.unknown-boundary
declared_effects:
- id: acme.network_io
timing: immediate
certainty: definite
- id: acme.audit_log
timing: deferred
certainty: possible

The engine embeds 42 reviewed packs: Scala case-class and Lombok and getset generator rules, JDK 21 result-use obligations, Node runtime-values, child-process, and Buffer packs, Python process-input and os.system packs, and Go standard-library, concurrency, and Testify packs. Hosts register them when they bootstrap the catalog. Most activate when the workspace contains the pack’s language. The eight Node packs set safety.review_required and also need an enable entry. The shipped packs table lists each pack, what it models, and what activates it.

The embedded registry ships reviewed Node runtime packs for JavaScript and TypeScript: a runtime-values pack per language modeling the process global exposure with env static-property and argv static-index keyed-read behaviors, and a child-process summaries pack modeling the exact execSync(command) member. TSX workspaces use the TypeScript packs.

With the reviewed summaries active, the engine consumes them for dispatch: a require-bound or namespace-bound receiver whose exact external member the resolver proved closes its residual dynamic-dispatch arm, the declaration pack’s dispatch reports exhaustive coverage, and a policy run whose only external callee is the modeled call concludes ProvenBySummary with definite findings. Complete stays reserved for proof derived from analyzed code, so a summary-closed run never claims it. A member write or a module-object escape anywhere in the workspace refuses the closure, and a callee the activated packs do not summarize keeps the run inconclusive.

Runtime-values activation is a layered contract. An exposure authored as enabled is intrinsically eligible within its model; it never bypasses pack activation. Every shipped Node runtime pack sets safety.review_required, so activation additionally needs exact host-supplied evidence — language, npm ecosystem, the exact Node artifact coordinates and digest, and the reviewed profile configuration — plus an explicit compatible enable control naming the pack id. The activation report retains that enable decision, and published endpoints retain the exposure, behavior, and active-model-set identities.

A workspace supplies that evidence by declaring the reviewed Node revision in its root package.json (engines.node or volta.node), .nvmrc, or .node-version. The declaration is code-runtime evidence about the analyzed program’s runtime, not a configuration document: it is how the workspace states that the code under analysis runs on the reviewed 22.11.0 distribution. Only that exact revision resolves to the reviewed archive digest and profile configuration, so no other pin, and no enable entry alone, can select the pack.

Authored packs carry no binding evidence or observations. Only workspace-derived analyzer evidence can authoritatively describe lexical bindings, writes, keyed loads, and observation identity in a particular workspace, so the production evaluator joins the authored exposure and behavior records with the workspace’s own syntax and executable-semantic facts. Lexically bound roots are conclusively excluded; reassignment, dynamic keys, unsupported runtimes and profiles, and incomplete analysis stay typed incomplete. Configuration documents can neither activate nor intersect the code runtime-value domain; a .bifrost/packs.json entry names a pack for the review gate but is not runtime evidence.

Compilation expands defaults, sorts semantic sets by stable ID, preserves ordered parameters/capture paths/concatenation operands, and serializes compact canonical JSON. Comments, YAML versus JSON, whitespace, and authored object order therefore do not affect the compiled semantic bytes. Changing an ordered parameter does.

The manifest remains uncompressed and readable. Each descriptor records the payload kind, routing keys, raw and stored sizes, record count, declared and referenced stable-ID inventories, encoding, and three lowercase SHA-256 digests:

  • semantic_sha256 identifies activation, compatibility, payload, completeness, and safety after normalization. Provenance, license, and storage encoding do not change it.
  • content_sha256 identifies the complete uncompressed shard, including provenance, license, producer, and pack version.
  • stored_sha256 identifies the bytes actually transported or stored and therefore changes when encoding changes.

The manifest has its own semantic_sha256 over the ordered shard semantic identities and a content_sha256 over the entire manifest view except that content field itself. The content digest therefore binds producer, provenance, license, compatibility, inventories, routing, sizes, encodings, and every shard digest while allowing those non-semantic fields to remain outside semantic identity.

content_sha256, stored_sha256, and the manifest content_sha256 are ordinary SHA-256 of the exact byte sequence named above. Semantic hashes use domain-separated length framing: SHA256(u64be(domain_length) || domain || u64be(byte_length) || bytes). The shard domain is bifrost.semantic-model.shard.semantic.v1; the manifest domain is bifrost.semantic-model.manifest.v1. “Canonical JSON” here means the compact UTF-8 field order emitted by this schema’s compiler, not RFC 8785.

Automatic storage uses fixed raw DEFLATE level 6 only when it saves at least 1 KiB and at least five percent. Otherwise the descriptor points to raw canonical JSON. Encoding never changes semantic or content identity.

Default compilation limits source, each raw shard, and each stored shard to 64 MiB; the readable manifest to 16 MiB; the pack to 1 GiB raw; the pack to 4,096 shards and two million records; each shard to 250,000 records; strings to 16 KiB; and recursive type/template structures to depth 64. Callers can lower these limits. Default compiler output is therefore accepted by the matching default decoder limits.

Decoding checks the manifest and declared sizes before allocation, validates the stored digest before decompression, streams DEFLATE into a bounded buffer, requires the exact raw size and content digest, validates the authored semantics, and re-normalizes decoded values to reject non-canonical JSON or semantic-set ordering. Shard ID, payload kind, routing keys, declaration inventories, record count, and semantic digest must agree with the descriptor. Manifest decoding checks pack-wide declaration uniqueness and references; manifest-bound shard decoding also requires every duplicated envelope field to agree. Truncation, trailing compressed data, excessive expansion, corruption, invalid semantics, or version mismatch fails closed.