vendkit docs

Architecture

1. Layering

Layer 3  Adoption surface      scaffolder (init), conformance rule specs,
                               operator documentation
Layer 2  Platform integration  ADO step templates / GHA composite actions +
                               reusable workflows — thin, parameters-only
Layer 1  Platform adaptation   CI output surface (in-process dialects) +
                               handler executables (vendor services: PR,
                               work items, fact verification) behind the
                               exec handler protocol
Layer 0  Core engine           declaration parsing, manifest, materialise,
                               gate verification, version compare, migrations,
                               conformance evaluation, git-protocol upstream
                               reads  (one static Go binary; git + filesystem only)

Rules that keep the layers honest:

  • Layer 0 calls no vendor service — its externals are git, the filesystem, and handler subprocesses; it never inspects CI environment variables except through ci.detect(). Its outputs are neutral: exit codes, key=value lines on stdout, JSON documents.
  • Layer 1 is the only place vendor knowledge appears, split by the format-vs-service rule (DR-0015): in-process code may understand vendor formats (output dialects, pipeline YAML, CODEOWNERS syntax); vendor services live exclusively in handler executables behind the handler protocol (DR-0014).
  • Layer 2 contains no logic. A template/action validates parameters, calls the CLI, and maps neutral outputs to platform outputs via the CI surface. If a template needs an if, the condition belongs in Layer 0 or 1.
  • Layer 3 is data plus rendering. The scaffolder renders CI-keyed template sets; conformance rules are YAML evaluated by the Layer 0 engine.

See specs/platform-integration.md for the CI surface and credential model, specs/handler-protocol.md for the delivery boundary, and DR-0007/DR-0014 for why the boundary sits exactly here.

2. Components and data flow

flowchart LR
  subgraph Publisher repo
    D[export declaration] --> G[vendkit generate]
    G --> M[(publisher manifest)]
    R[vendkit release] -->|annotated tag vX.Y.Z| T[(immutable tag)]
    M -->|freshness pre-gate| R
    MIG[migrations/*.yml] --- T
    CS[conformance spec] --- T
  end

  subgraph Consumer repo
    W[watch] -->|pinned < latest| H[handoff handler → work item / issue]
    S[sync lane] -->|materialise target release| PR[PR handler → reviewed sync PR]
    GATE[gate lane] -->|every PR| OK{merge allowed}
    MR[migration resolve] --> H2[migration handoff] --> V[migration verify]
    C[conformance check] --> H
    CFG[.vendkit/consumer/&lt;slice&gt;.yml] --- W & S & C
    CM[(.vendkit/consumer/&lt;slice&gt;-manifest.json)] --- GATE & S
  end

  T -->|pull: schedule / push: release trigger| S
  T -->|git ls-remote --tags| W

Component inventory, each with its own spec:

Component Layer Spec
Export declaration 0 (schema) specs/export-declaration.md
Manifest + gate lane 0 specs/manifest-and-gate.md
Materialise / sync lane 0 (+1 PR handler) specs/sync.md
Release cutter 0 specs/releases-and-versioning.md
Release watch 0 (+1 handoff handler) specs/release-watch.md
Migrations 0 (+1 handoff handler) specs/migrations.md
Conformance 0 (formats only) specs/conformance.md
Handler protocol + reference handlers 1 specs/handler-protocol.md
CI surface, credentials, differences ledger 1 specs/platform-integration.md
Templates / actions 2 specs/platform-integration.md §5
Scaffolder + consumer config 3 specs/onboarding.md
CLI 0 surface specs/cli.md

3. The invariants

Numbered; other specs cite them as INV-n. The conformance kit (testing.md) turns each into an executable check.

  • INV-1 (Composition). For any release, declaration, profile and consumer tree: sync apply produces a tree and manifest that pass gate verify --strict with zero findings.
  • INV-2 (Purity). materialise output is a pure function of (release tree, export declaration, consumer profile, current consumer manifest). No clock, no network, no environment dependence.
  • INV-3 (Prediction). sync check reports exactly what sync apply would write. A successful check always exits 0 and prints changed=true|false; any non-zero exit is an infrastructure failure — a crash can never masquerade as staleness (the porcelain contract).
  • INV-4 (Refresh by default). Without explicit scope reconciliation, sync refreshes the currently tracked file set only. Scope changes (additions via reconcile, removals detected upstream) are always surfaced in a reviewed PR; files are never deleted from disk automatically.
  • INV-5 (Immutability). A released tag is never moved or reused. A fix is a strictly newer release. Consumers additionally record the resolved commit SHA so tag substitution is detectable (see security model).
  • INV-6 (Engine-version). Materialise always runs the target release's engine (the pinned platform reference resolves the same tree that supplies both content and engine). Gate verification always runs against the pinned release's manifest. There is no version skew inside either operation. With the compiled engine shipped (DR-0017), this holds as an explicit, checksummed engine pin with schema-competence gating (DR-0016): the scaffolded lane fetches and self-verifys the pinned binary, and the sync PR advances the engine pin in lockstep with content. The human-tier CLI documents its own relaxation (cli spec).
  • INV-7 (Disjointness). Across all slices vendored by one consumer, the consumer_path sets are pairwise disjoint. Checked by the gate lane on every PR.
  • INV-8 (Neutral core). Layer 0 behaves identically on every platform and in no-CI (local/dev) execution — it calls no vendor service (DR-0014). Platform behaviour differences are confined to Layer 1 (CI surface, handlers, dialect parsing) and are enumerated in the platform-integration spec's differences ledger.
  • INV-9 (Dependency-free gate). The consumer-side gate path runs a single static binary with no runtime prerequisites at all (DR-0016) — no interpreter, no third-party packages, no YAML parsing (the gate reads only the JSON manifest).
  • INV-10 (Review sovereignty). No machinery ever merges, pushes to a protected branch, or mutates consumer-owned content directly. Every change lands as a PR under the consumer's normal review rules.

4. Repository layout

vendkit/                    # one Go module, one static binary (DR-0017)
  cmd/vendkit/              # single entrypoint `vendkit` + subcommand dispatch:
    main.go                 #   dispatch, flag plumbing, exit-code conventions
    cmds.go / human.go      #   CI-tier and human-tier commands
    handler.go              #   Layer 1: `vendkit handler <scm>` reference
    │                       #   PR/handoff/fact delivery (github, ado)
    selfverify.go           #   `vendkit self-verify` engine-pin check (DR-0016)
  internal/core/            # Layer 0 engine: declaration, manifest, materialise,
    │                       #   verify, versions, migrations, conformance,
    │                       #   upstream (git-protocol reads), handler invocation
  internal/ci/              # Layer 1 CI output surface — in-process dialects
  internal/e2e/             # Go unit + end-to-end scenario kit; journalhandler
    │                       #   is the neutral reference handler for tests
  assets.go                 # embeds scaffold/ + conformance rules into the binary
  scaffold/
    azure-pipelines/*.tmpl  # Layer 3: consumer pipeline scaffolds (ADO)
    github-actions/*.tmpl   # Layer 3: consumer pipeline scaffolds (GHA)
                            #   fetch + checksum-verify the pinned engine binary,
                            #   then run it (DR-0016); ci: none scaffolds none
  conformance/
    core-rules.yml          # platform-neutral wiring rules shipped by default
  .github/workflows/        # this repo's CI + SemVer release (checksummed
    │                       #   per-platform binaries + SHA256SUMS.txt, DR-0016)
  vendkit-export.yml        # this repo's own export declaration (self-hosted)
  vendkit-manifest.json     # generated + freshness-checked by the binary
  docs/                     # this spec set, maintained as the docs of record

Layer 2 wrapper packaging (platforms/) is deferred (see roadmap): the scaffolded consumer pipelines invoke the pinned vendkit binary directly — same guarantees, fewer moving parts.

Self-hosting. The framework repository is its own first publisher: it exports its pipeline scaffolds and core conformance rules as a slice, cuts releases with its own release command, and gates itself with its own gate lane. The engine is not part of the slice — it reaches every tier as the pinned, checksummed release artefact (DR-0016, DR-0020). Downstream publishers vendor the machinery slice and layer their own content slices on top (tier chain). Bootstrap is unproblematic: the release freshness pre-gate needs only the working tree.

5. Multi-slice consumers

A consumer vendoring N slices holds, per slice: one manifest (.vendkit/consumer/<slice>-manifest.json), one config (.vendkit/consumer/<slice>.yml), one sync pipeline, and a pin inside that pipeline. It holds one gate-lane pipeline total: the gate discovers every .vendkit/consumer/*-manifest.json, verifies each, and enforces INV-7 across them. Watch is likewise a single pipeline reading every slice config. Sync pipelines stay per-slice because cadence, credentials and push triggers are per-publisher decisions.

6. The docs site

These docs are published as a versioned static site so a reader sees the docs for the release they care about, not always main. The pipeline is pure Go — no Python, Node, or mkdocs (DR-0017, DR-0018).

Generator. internal/docsgen is a build-time package main (never imported by cmd/vendkit, so the shipped binary is unaffected). It renders the docs/ Markdown tree to self-contained HTML with goldmark (GFM tables included), rewrites internal *.md links to the rendered *.html paths, and emits a left nav plus a version-selector dropdown on every page.

Build the site locally:

# Render one version into ./dist (repeat per release; dist/ is gitignored).
go run ./internal/docsgen --docs docs --out dist --version v0.1.0
go run ./internal/docsgen --docs docs --out dist --version v0.2.0
# dist/v0.1.0/  dist/v0.2.0/  dist/latest/  dist/versions.json

go run ./internal/docsgen --help documents the flags.

Versioning. Each run writes dist/<version>/ (one page per doc plus an index.html), merges the version into dist/versions.json (newest first, the newest marked latest by SemVer), and mirrors the newest version into dist/latest/. Output is deterministic (stable ordering, no timestamps), so re-running a version is idempotent and diffs are clean. The in-page selector fetches versions.json at runtime, so a page published for an older release still lists releases added later.

Publishing. .github/workflows/pages.yml accumulates on a gh-pages branch: on a vX.Y.Z tag it seeds from the currently published site, renders that version into it, and republishes the whole tree — previously published versions stay put. A push to main re-overlays the hand-authored landing page (site/index.html) at the site root. The landing page's "Docs" links point at /latest/.