5. A pattern declares what is vendored#
Status#
Accepted
Context#
ibek pattern hard-coded its vendoring policy: every file in a pattern folder
was copied into <instance>/config/ and hashed into runtime-lock.yaml. That is
exactly right for a flat StreamDevice pattern and wrong for everything else.
The immediate driver is documentation. We want the useful docs from each source
module to live in ibek-runtime-streamdevice alongside the protocol and template
files, so the library is browsable and self-explanatory. Those docs must not be
vendored into an IOC instance — they are repo context, not runtime files, and
config/ is the Kubernetes ConfigMap payload. There was no way to express that.
The second driver is forward compatibility with build-time support management, which needs the same declarative description of “which files go where” against a different library and a different destination.
Decision#
A pattern folder may carry an ibek.manifest.yaml declaring which of its
files are vendored and where they land:
version: 1
vendor:
- src: '.*\.(template|proto|protocol|db|req|ibek\.support\.yaml)$'
dest: config
It is an allow-list. A file matched by no entry is not vendored. That is the mechanism that keeps
docs/out ofconfig/.The list is ordered and first-match-wins. Without that, one file could land in two places under two lock keys, and both
checkand orphan-pruning become ambiguous about which copy is authoritative.A pattern with no manifest is vendored through a synthesised default (
src: '.*',dest: config) rather than through a second code path. One vendoring path, one check path, no branches — and the default is covered by the same validation and the same tests as a hand-written manifest. A pattern gets a manifest if and only if it contains files that must not be vendored.Validation is whole-manifest and fails hard, before a single file is written. A skipped-with-warning entry would mean a file that should be in the IOC silently is not, which is not acceptable in an integrity system. Every message names the offending entry.
Lock keys are relative to the destination root, always — not to
config/. The synthesised default (a pattern with no manifest) producesconfig/...keys naturally, so there is no per-pattern base field and no branch incheck.
The lock has a version: / patterns: root wrapper, because a bare
{pattern_name: entry} mapping has nowhere to put metadata: version: gives
the format room to evolve, patterns: holds each vendored pattern by name.
ibek reads and writes exactly this one shape. A lock in any other shape is
refused outright, with a generic message naming a standalone conversion
script rather than describing what the other shape is or how it arose — a
description that would go stale the moment the lock format changes again.
Reading such a lock as an empty one is the one thing that must never happen:
check would then pass having verified nothing, and the instance would
silently become unmanaged.
The lock’s version: is 1, or 2 when any pattern carries a select: (an
instance’s own include / exclude adjustment to the manifest). Version 2 is
version 1 plus that key, and ibek writes the lowest version that holds the
content, so a lock without a selection reads the same to every ibek, while one
that could not apply a selection refuses the lock on its version rather than
vendoring or checking a different file-set. Unknown top-level keys are refused
for the same reason: ignored on read, they would be dropped on the next save.
Consequences#
Switching a pattern’s manifest can prune an emptied
config/itself, since orphan pruning now walks up to the destination root. Correct, but startling in a diff.Orphans are computed from the old lock’s key list and new files from the new plan, so
updateacross a version that gains a manifest removes exactly the files the manifest stopped matching.destmay also be a substitution (\1/\g<name>), expanding to the whole destination-relative path for flattening, renaming or re-rooting. It is kept out of the user-facing tutorial:dest: configis the only form a services repo needs...is therefore checked after expansion, since a capture group can introduce one.A vendored
*.ibek.support.yamlmust land at theconfig/root, becauseibek pattern schemamergesconfig/*.ibek.support.yamlnon-recursively. A misrouted one vendors cleanly, checks cleanly, and is then silently missing from the instance’s editor schema. (ibek runtime generate2globsconfig/**/*.ibek.support.yamland does find a nested one, so the IOC itself still boots — what is lost is editor validation.) The rule is conditional on the file being vendored at all.Two sources mapping to one destination is a hard error, not a last-writer-wins overwrite that
checkwould then pass — and destinations are compared in normal form, soconfig/xandconfig/./xare recognised as the one file they are. Likewise one destination cannot be both a file and the folder holding another, which would otherwise fail half way through the write.destis the only user-controlled placement, andibek runtime place-filescopies*.proto/*.db/*.templatefrom theconfig/root only. Nesting those underconfig/is for repo organisation; they will not reach the IOC’s runtime search path. Closing that gap belongs toplace-files, not here.
Rejected#
A
lock:key in the manifest. A pattern can never be the authority on where a destination’s lock lives.runtime-lock.yamlat the destination root, always.A
stamp:key — moot once the vendored header is dropped (see ADR 4).A per-pattern
root:field in the lock, or a “has manifest” flag. Destination-root-relative keys make both unnecessary, andcheckis offline and cannot see the manifest anyway. The version pin already pins it.Making the lock’s
version:a switch ibek branches on to read an older shape. It exists for future evolution and validation only; a shape ibek does not recognise is refused, and converting it is a standalone step outside ibek entirely (scripts/convert-runtime-lock.py).A
{pattern}token indest, arequires:key, dependency resolution, and one lock spanning several destinations. All belong to the build-time support issue, and none of them need a format break — which is why the manifest root is a mapping rather than a bare list.
See ADR 3 for where the authority for a pattern’s content lives, and the pattern manifest reference for the format.