Vendor a module by hand
Bootstrap a module dependency with no repository to fetch from by copying its source tree into aontu_meta/vendor/ and letting aontu sync pin what it means.
Rendered from
docs/how-to/vendor-by-hand.md
in the engine repository, where a correction belongs, and where the
test suite executes every example on this page.
aontu sync fetches a package from a repository, and a package that
was never published to one cannot be fetched. The content-addressed
user cache cannot be searched until a lockfile pins a hash, either, so
the cold start for a package that lives only in a colleague’s checkout
is hand-vendoring: put its source tree into aontu_meta/vendor/
yourself, then let sync pin it. cp -r is the distribution protocol.
Start on the consumer side. The project declares the dependency in
its pkg.aon:
pkg: { path:"corp.example/app" main:"main.aon" }
dep: "corp.example/schemas/service": v: "1.0.0"
and the entry file main.aon imports it:
svc: @"corp.example/schemas/service"
svc: name: "auth"
With nothing received yet, aontu pkg tidy, the resolve step that
fetches nothing, refuses and names the module:
$ aontu pkg tidy
verdict: missing
corp.example/schemas/service: not fetched (run: aontu sync)
$ echo $?
1
No lockfile is written; a partial lock would claim a closure that was
never resolved. So do the fetch’s job by hand. The layout is
aontu_meta/vendor/<package-path>/ under your project, beside its
pkg.aon: each /-segment of the package path becomes a directory,
and an uppercase letter is written ! followed by its lowercase:
project/
pkg.aon
main.aon
aontu_meta/
vendor/
corp.example/
schemas/
service/
pkg.aon
service.aon
The directory holds the package’s own source tree. Its
aontu_meta/vendor/corp.example/schemas/service/pkg.aon:
pkg: { path:"corp.example/schemas/service" version:"1.0.0" main:"service.aon" }
and its entry file,
aontu_meta/vendor/corp.example/schemas/service/service.aon:
name: string
port: *8080|integer
Now sync, from the project root. It finds the hand-made tree at the
version the project asked for, evaluates the module standalone, locks
its canon-hash and the digest of its archive, and verifies both;
evaluation then resolves the import:
$ aontu sync
verdict: ok
corp.example/schemas/service 1.0.0 aon1-oQs6Ng6XxP2FHQGTYescREGDrDPfLLW1Liq4OS8Gs2E
$ aontu main.aon
{
"svc": {
"name": "auth",
"port": 8080
}
}
The pin is what the hand-vendoring was for. Every later evaluation
re-derives the vendored module’s canon-hash and compares it to the
locked one, so a change to the module’s evaluated meaning is refused
rather than silently used. Flip the vendored default in
aontu_meta/vendor/corp.example/schemas/service/service.aon:
name: string
port: *9090|integer
$ aontu main.aon
module integrity: corp.example/schemas/service expected aon1-oQs6Ng6XxP2FHQGTYescREGDrDPfLLW1Liq4OS8Gs2E got aon1-Bd4OQlOyzyJcXZvYbVcV7NZbMJGGxQH6GtNctkC26VA
$ echo $?
1
Both hashes are named: the meaning that was reviewed and the meaning
the store now holds. Be precise about what that pin protects. It is a
semantic pin, taken over the canonical form of the module’s entry
document and its include closure; comments, whitespace, refactored
spellings that canon to the same value, pkg.aon metadata, and files
the entry never includes all keep the hash, deliberately. The bytes are
the archive pin’s job, and the tooling checks that one first.
In CI, verify: do not tidy
tidy rewrites the lockfile from whatever the store currently holds,
so a job that tidies before evaluating makes the lock agree with a
tampered store and then passes. aontu pkg verify asks the question
without answering it by editing: against the still-tampered store, the
bytes are checked before the meaning, so it is the archive pin that
speaks:
$ aontu pkg verify
verdict: mismatch
corp.example/schemas/service: pinned archive sha256:db1c3797fbf11badbc4fb7c87c6a4c34fafe602735fb63dbdccc7de951f3d5e8 but the store holds sha256:d5e16bacd6172fd3f585fb8ec75d720c5653f277f301792bf0470edf19e2e6f2
$ echo $?
1
It recomputes every pin, compares against the committed lockfile,
writes nothing, and exits 1 on any disagreement. Run it beside your
tests (validate in CI is the surrounding job), or
run aontu sync --frozen, which refuses the same way and fetches what
a locked version needs; run sync without the flag only when you
intend to move a pin, and review its diff like code. Nothing to check
is not a pass, either. Take a project that declares the dependency but
never committed a lockfile: only its pkg.aon:
pkg: { path:"corp.example/app" main:"main.aon" }
dep: "corp.example/schemas/service": v: "1.0.0"
$ aontu pkg verify
verdict: unlocked
corp.example/schemas/service: not in the lockfile (run: aontu sync)
$ echo $?
1
An uncovered project is refused rather than verified over an empty set, and the line names the repair.
A package with its own dependencies vendors flat
A vendored package carries its own pkg.aon and may declare its own
dep. Its imports resolve from its own directory and from every
enclosing project root, so its dependency goes in the same
aontu_meta/vendor/ tree, beside it: never nested inside it:
project/
pkg.aon
aontu_meta/
vendor/
corp.example/
schemas/
service/ # imports common
pkg.aon
service.aon
common/ # flat beside it, not nested inside it
pkg.aon
common.aon
Declaring only the top of the closure is enough, because sync walks
the rest. A consumer pkg.aon:
pkg: { path:"corp.example/app" main:"main.aon" }
dep: "corp.example/schemas/service": v: "1.0.0"
its entry main.aon:
svc: @"corp.example/schemas/service"
svc: name: "auth"
a hand-vendored
aontu_meta/vendor/corp.example/schemas/service/pkg.aon that declares a
dependency of its own:
pkg: { path:"corp.example/schemas/service" version:"1.0.0" main:"service.aon" }
dep: "corp.example/schemas/common": v: "1.0.0"
and its entry
aontu_meta/vendor/corp.example/schemas/service/service.aon, importing
it:
@"corp.example/schemas/common"
name: string
port: *8080|integer
With common not yet vendored, tidy refuses the whole closure:
$ aontu pkg tidy
verdict: error
corp.example/schemas/service: does not evaluate on its own; nothing to pin
corp.example/schemas/common: not fetched (run: aontu sync)
$ echo $?
4
A module that does not evaluate on its own is refused rather than
pinned, because every module that fails to evaluate hashes to the
same string: a lockfile written from one would look like a pin and
mean nothing. (aontu hash refuses the same file with the same
wording.) Vendor the dependency flat beside its dependant:
aontu_meta/vendor/corp.example/schemas/common/pkg.aon:
pkg: { path:"corp.example/schemas/common" version:"1.0.0" main:"common.aon" }
with its entry, a shared naming vocabulary, as
aontu_meta/vendor/corp.example/schemas/common/common.aon:
name: string & re("^[a-z][a-z0-9-]*$")
and the closure resolves, both packages pinned:
$ aontu sync
verdict: ok
corp.example/schemas/common 1.0.0 aon1-btDT9RfDGjP4uvd5osF3R3mRW5aIeDz49_JbJpVLDwU
corp.example/schemas/service 1.0.0 aon1-GublSGsGCwYBgyQBAZSk9imd7xfbeCYKY6qbud8okdc
$ aontu main.aon
{
"svc": {
"name": "auth",
"port": 8080
}
}
One caution to carry away: the first copy is trusted axiomatically.
There is no signed manifest to compare a hand-vendored tree against,
so review what you vendor as if it were your own code; every copy
after that is held to the first by the pins. A package that has been
published carries its publisher’s proof instead: that is
publish a package. The verbs’ full contract is
aontu sync and
aontu pkg, and the live version of
all of this (tamper, refactor-stable hashes, trust confinement, the
publish gate) is
use-cases/11-shared-modules.