boatyard 0.6.0.dev170__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boatyard-0.6.0.dev170/.gitattributes +1 -0
- boatyard-0.6.0.dev170/.github/workflows/cleanup-pr-releases.yml +15 -0
- boatyard-0.6.0.dev170/.github/workflows/integrate.yml +24 -0
- boatyard-0.6.0.dev170/.github/workflows/merge-gate.yml +19 -0
- boatyard-0.6.0.dev170/.github/workflows/release.yml +34 -0
- boatyard-0.6.0.dev170/.gitignore +12 -0
- boatyard-0.6.0.dev170/.python-version +1 -0
- boatyard-0.6.0.dev170/AGENTS.md +195 -0
- boatyard-0.6.0.dev170/CLAUDE.md +1 -0
- boatyard-0.6.0.dev170/LICENSE +201 -0
- boatyard-0.6.0.dev170/NOTICE +1 -0
- boatyard-0.6.0.dev170/PKG-INFO +12 -0
- boatyard-0.6.0.dev170/README.md +3 -0
- boatyard-0.6.0.dev170/closeout.md +518 -0
- boatyard-0.6.0.dev170/design/AGENTS.md +22 -0
- boatyard-0.6.0.dev170/design/CLAUDE.md +1 -0
- boatyard-0.6.0.dev170/design/adoption-semver-wave.md +31 -0
- boatyard-0.6.0.dev170/design/apt-hermeticity.md +109 -0
- boatyard-0.6.0.dev170/design/dev-overlay.md +17 -0
- boatyard-0.6.0.dev170/design/gpu-template-collapse.md +21 -0
- boatyard-0.6.0.dev170/design/mirror-digest-retention.md +85 -0
- boatyard-0.6.0.dev170/design/precision-attribution.md +90 -0
- boatyard-0.6.0.dev170/design/semver-image-bundle-lines.md +110 -0
- boatyard-0.6.0.dev170/publish.yml +6 -0
- boatyard-0.6.0.dev170/pyproject.toml +43 -0
- boatyard-0.6.0.dev170/ruff.base.toml +204 -0
- boatyard-0.6.0.dev170/ruff.toml +1 -0
- boatyard-0.6.0.dev170/scripts/preflight.py +3 -0
- boatyard-0.6.0.dev170/src/boatyard/__init__.py +0 -0
- boatyard-0.6.0.dev170/src/boatyard/build.py +267 -0
- boatyard-0.6.0.dev170/src/boatyard/cli.py +23 -0
- boatyard-0.6.0.dev170/src/boatyard/compose.py +24 -0
- boatyard-0.6.0.dev170/src/boatyard/config/__init__.py +0 -0
- boatyard-0.6.0.dev170/src/boatyard/config/config.py +787 -0
- boatyard-0.6.0.dev170/src/boatyard/config/lock.py +152 -0
- boatyard-0.6.0.dev170/src/boatyard/config/values.py +188 -0
- boatyard-0.6.0.dev170/src/boatyard/crane.py +23 -0
- boatyard-0.6.0.dev170/src/boatyard/dockerfiles.py +201 -0
- boatyard-0.6.0.dev170/src/boatyard/lock.py +233 -0
- boatyard-0.6.0.dev170/src/boatyard/markers.py +201 -0
- boatyard-0.6.0.dev170/src/boatyard/paths.py +5 -0
- boatyard-0.6.0.dev170/src/boatyard/publish.py +72 -0
- boatyard-0.6.0.dev170/src/boatyard/py.typed +0 -0
- boatyard-0.6.0.dev170/src/boatyard/score.py +267 -0
- boatyard-0.6.0.dev170/src/boatyard/score_compose.py +274 -0
- boatyard-0.6.0.dev170/src/boatyard/score_k8s.py +152 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/gpu-cuda.tpl +24 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/gpu-pod.tpl +43 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/gpu-rocm.tpl +30 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/probe-adapter-healthchecks.tpl +55 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/restart.tpl +21 -0
- boatyard-0.6.0.dev170/src/boatyard/score_patch_templates/scale-to-zero.tpl +30 -0
- boatyard-0.6.0.dev170/src/boatyard/temp.py +5 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/__init__.py +0 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/build.py +54 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/down.py +39 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/lock.py +34 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/matrix.py +57 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/mirror.py +50 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/publish.py +138 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/score.py +161 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/tree_tags.py +35 -0
- boatyard-0.6.0.dev170/src/boatyard/verbs/up.py +265 -0
- boatyard-0.6.0.dev170/src/boatyard/yaml.py +34 -0
- boatyard-0.6.0.dev170/tests/conftest.py +3 -0
- boatyard-0.6.0.dev170/tests/test_assembly.py +283 -0
- boatyard-0.6.0.dev170/tests/test_build_docker.py +847 -0
- boatyard-0.6.0.dev170/tests/test_compose.py +1606 -0
- boatyard-0.6.0.dev170/tests/test_config.py +1536 -0
- boatyard-0.6.0.dev170/tests/test_daemon_gc.py +83 -0
- boatyard-0.6.0.dev170/tests/test_lock.py +1139 -0
- boatyard-0.6.0.dev170/tests/test_markers.py +297 -0
- boatyard-0.6.0.dev170/tests/test_matrix.py +143 -0
- boatyard-0.6.0.dev170/tests/test_mirror.py +357 -0
- boatyard-0.6.0.dev170/tests/test_provisioners.py +48 -0
- boatyard-0.6.0.dev170/tests/test_publish_compose.py +431 -0
- boatyard-0.6.0.dev170/tests/test_score_generate.py +1380 -0
- boatyard-0.6.0.dev170/tests/test_secrets.py +45 -0
- boatyard-0.6.0.dev170/tests/test_service_builds.py +412 -0
- boatyard-0.6.0.dev170/tests/test_tree_tags.py +107 -0
- boatyard-0.6.0.dev170/uv.lock +423 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruff.base.toml -text
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: Cleanup PR releases
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
cleanup-pr-releases:
|
|
12
|
+
if: github.event.pull_request.merged == false
|
|
13
|
+
uses: outernet-foundation/github-actions-toolkit/.github/workflows/cleanup-pr-releases.yml@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
14
|
+
with:
|
|
15
|
+
pr-number: ${{ github.event.pull_request.number }}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Integrate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [dev]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: integrate-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
preflight:
|
|
17
|
+
uses: outernet-foundation/github-actions-toolkit/.github/workflows/preflight.yml@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
18
|
+
|
|
19
|
+
release-pr-builds:
|
|
20
|
+
needs: [preflight]
|
|
21
|
+
permissions:
|
|
22
|
+
contents: write
|
|
23
|
+
packages: read
|
|
24
|
+
uses: outernet-foundation/github-actions-toolkit/.github/workflows/release-pr-builds.yml@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Merge gate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [dev]
|
|
6
|
+
types: [labeled]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: merge-gate-${{ github.event.pull_request.number }}
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
packages: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
merge-gate:
|
|
17
|
+
uses: outernet-foundation/github-actions-toolkit/.github/workflows/merge-gate.yml@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
18
|
+
secrets:
|
|
19
|
+
merge-bot-private-key: ${{ secrets.MERGE_BOT_APP_PRIVATE_KEY }}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: release-${{ github.ref }}
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
prerelease:
|
|
15
|
+
if: github.ref == 'refs/heads/dev'
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: write
|
|
19
|
+
id-token: write
|
|
20
|
+
steps:
|
|
21
|
+
- uses: outernet-foundation/github-actions-toolkit/.github/actions/release@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
22
|
+
with:
|
|
23
|
+
channel: dev
|
|
24
|
+
|
|
25
|
+
release:
|
|
26
|
+
if: github.ref == 'refs/heads/main'
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: outernet-foundation/github-actions-toolkit/.github/actions/release@c6221cf68594e2d6bf4445da02beea0b8ee58440
|
|
33
|
+
with:
|
|
34
|
+
channel: stable
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# boatyard
|
|
2
|
+
|
|
3
|
+
Docker-stack lifecycle verbs as a generic package for any repo shipping a compose graph; `up` derives the whole assembly from the root `boatyard.yml`. No Python
|
|
4
|
+
API — the contracts are the verb CLI (`boatyard <verb>`), the declaration schema (`boatyard.yml` + `boatyard.lock.yml`), and committed artifact formats. Three-class principle: authored facts in the config, registry-resolved in the lock, tree-derived
|
|
5
|
+
(`tree-<hash>`) computed on demand, never committed. PyPI identity starts at `0.1.0`; deps resolve from PyPI, git-source pins only in scratch branches. github-actions-toolkit owns the consumer workflow contract and is never a project dependency (resolver cycle); from 1.0 on breaking changes ship with a manually bumped `major_minor`. Fresh-repo assumption: no code handles an obsolete format
|
|
6
|
+
— a past artifact gets the plain parse refusal, never a migration path.
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
Quick loop: `uv run pytest`, `uv run ruff check`; full gate: `uv run --locked scripts/preflight.py`.
|
|
11
|
+
|
|
12
|
+
## Verbs
|
|
13
|
+
|
|
14
|
+
One module per command under `verbs/`, each with its local bare `app = typer.Typer()` + `@app.command()` on the function; `cli.py` composes them with nameless `app.add_typer(...)` — flat top-level verbs (a named `add_typer` nests a group); one `boatyard`
|
|
15
|
+
console script. Exception law: nothing is ever caught only to be re-raised as a different exception with added information — hand-raised guards and third-party exceptions surface raw, uncaught, with full traceback; `typer.BadParameter`
|
|
16
|
+
owns the CLI-flag layer; `raise SystemExit` only in `verbs/publish.py` (registry credentials), `verbs/score.py` (stale artifacts), and `lock.py` (stale lock). Flags live in the typer signatures in `verbs/*.py`; the flag laws: `build`'s
|
|
17
|
+
`--push`/`--push-to` refuse together; `generate-score --target both` refuses (BadParameter) a one-substrate repo naming the matching narrower `--target` (the rendering pipelines carry no substrate-presence checks); `docker-build-matrix` answers a bare JSON array.
|
|
18
|
+
`--lock-only`/`--check`/`--upgrade` belong to `lock`, never `build`; no `--gpu`, no `--compose-file` (the verbs own the compose invocation); `tree-tags` answers a bare string — no JSON, no `{SERVICE}_SHA` names.
|
|
19
|
+
|
|
20
|
+
## Modules
|
|
21
|
+
|
|
22
|
+
`config/config.py` schema + load; `ConfigFile.resolve_services(choices)` folds a selection into leaf services; `dockerfiles.py` Dockerfile declarations + sha machinery (`ServiceBuild`, `derive_services`); `config/lock.py` lock schema + paired `LockFile.load(path, config)` + the projection members; `yaml.py` `load_yaml_model` + its duplicate-key walk (the YAML→pydantic parse boundary); `config/values.py` the typed values file; `paths.py` the consumer-repo document paths (`DEFAULT_CONFIG_PATH`/`VALUES_FILE`/`LOCK_PATH`); `temp.py` the shared digest/platform regex spellings
|
|
23
|
+
(`ValuesFile.load`, `PublicUrl`); `lock.py` relock engine + crane resolution; `compose.py` the compose invocation spelling `compose_command` (both verbs); `build.py` build pipeline; `markers.py` marker resolution; `score.py` score shared base; `score_compose.py`/`score_k8s.py` rendering pipelines; `verbs/score.py` the score verb + `ensure_tools`;
|
|
24
|
+
`publish.py` publish preparation; `crane.py` pinned crane installer; `verbs/up.py` selection + rendering inlined in `up` + the up-only `render_env_entries`/`lock_env_unwind`; `verbs/*` one module per command. Model modules run root → leaves: root record first
|
|
25
|
+
(`RootModel` subscripts quote forward refs; the one inversion is a parent whose field default constructs the child), functions callers-before-callees; imports absolute; `dockerfiles` ← `config/config` ← `config/lock` ← `lock` one-way. Also in `config/config.py` (the shared leaf): `Yaml`,
|
|
26
|
+
`is_mapping`/`is_list`, `double_dollars` (the one `$`-doubling home), `PLACEHOLDER`/`COMPOSE_SLOT`/`ENV_NAME_PATTERN`, `EDGE_SERVICE_NAME`, `lock_key_prefix`, `chart_lock_key`, `bundle_lock_key`, `env_lock_slot`/`LOCK_SLOT_MARKER`,
|
|
27
|
+
`compose_bundle_output_path` (the one compose-output path join), `qualified_option_name` (the one qualified-name join), `required_compose_slots` (the odd-parity slot scan over compose texts), `scan_bundle_texts`
|
|
28
|
+
(the refuse-then-requires wrapper answering `BundleScan`), `refuse_unresolved_references`
|
|
29
|
+
(the shared route/port refusal over compose texts; answers the services keys; `prefix` composes the `{entry}-` namespace).
|
|
30
|
+
|
|
31
|
+
## ConfigFile schema
|
|
32
|
+
|
|
33
|
+
- `extra="forbid"` everywhere; per-field shape laws in `field_validator`s, cross-field laws in one `model_validator(mode="after")` per model; both run in declaration order.
|
|
34
|
+
- `requires` self-checks the installed version at load (`0.0.0.dev0` dev sentinel bypasses the check, not the field); duplicate keys, unknown top-level keys, dead sections (`generate-score`, `publish-compose`, `lifecycle`, `bundles`,
|
|
35
|
+
`lifecycle_prefix`) refuse.
|
|
36
|
+
- `base_images`: ALL_CAPS keys become build args; upstream-native `path:tag`/`path@digest` values; `{uv}`/`{python}` templates over toolchain pins (`{uv}` from `tool.uv.required-version`, exact `==` pin only; `{python}` from
|
|
37
|
+
`.python-version`); a literal value in place of a template = the deliberate-skew escape hatch.
|
|
38
|
+
- `variants`: ALL_CAPS axis → bare lowercase values, first = the flagless build's default; declared, never probed. `platforms` required exactly when `base_images` is; charts arch-neutral, exempt.
|
|
39
|
+
- `project_name` required on every config; `gateway` is optional — routes require it, and declaring it requires `CADDY_IMAGE` and renders the edge, routes or not (caddy == gateway).
|
|
40
|
+
- `services`: groups (`options:` mutually exclusive) + bare riding entries; every entry/option is `ref:` (tag-pinned OCI include — digest/`latest`/range spellings refuse at load; any published channel spelling pins) or `workloads:`/`vars:`; workload references are flat `*.yaml` names (both sections; `metadata.name` pins on stems); an option's name outside its group is its
|
|
41
|
+
qualified `group-dash-option` name; the vocabulary (qualified + bare spellings) refuses duplicates at load; `auth` reserved — options declare exactly one route; routes `/`-rooted, edge-wide unique, `strip_prefix` requires a wildcard; ports use the mapping form (service, published, target).
|
|
42
|
+
- `k8s`: `workloads`, `vars`, `charts` name → `oci://…:tag|@digest`; the map key IS the `{NAME}_CHART` lock key; a section declaring neither workloads nor charts refuses.
|
|
43
|
+
- Load refusals (`ConfigFile.resolve`): Dockerfile services or local entries without `repository`; a declared chart without `stack/score/k8s/values/{chart}.yaml`; routes without `gateway`; a declared `gateway` without `CADDY_IMAGE` in `base_images` (the lock half of that law rides the paired read's coverage). Config load never reads the lock — the lock verb bootstraps a first lock from nothing; lock consumers refuse wholesale on drift at the paired read, a missing entry refuses per `require`, and both name the remedy.
|
|
44
|
+
- Every `load()` takes a full path and rides `load_yaml_model` (`yaml.py`: read → duplicate-key walk → `yaml.safe_load` → None→`{}` → `model_validate` with context); a file parsed straight into a pydantic model rides it too (secrets, tool state, compose output); a missing file surfaces as raw `FileNotFoundError` at the call site that failed to check — `LockFile.load` is the one exception (its missing-as-empty policy is stated under Lock). Missing-file policy lives at callers: `lock`/`matrix`/`score`/`publish` self-skip or answer empty via their own `is_file` gate; relock's committed read is schema-only inline (`load_yaml_model` in `lock.py` — drift and old formats are its input, never refused); `score_compose` reads `platform` as `None` when the values file is absent; `up` refuses without it at load; `down` never reads it.
|
|
45
|
+
- `mirrored_ref(reference, prefix)` = the only prefix join — injects the prefix, rejects an already-prefixed reference.
|
|
46
|
+
- Derivation shape law: `ConfigFile` carries authored data and validation; a member may hold real derivation logic (`local_service_entries`, `declared_charts`, `resolve_services`), but a member that only delegates to another module's function is a
|
|
47
|
+
pass-through — call the function at the consumer, `compute_service_shas` module-qualified (test seam); `root` defaults to the working directory, `resolve` injects the load root, excluded, never-authored.
|
|
48
|
+
- `boatyard.values.yaml` (consumer root, gitignored) is the only machine-values source — the ambient shell fills nothing; `up` refuses without it, score generation and `down` run without it (`score_compose` reads `platform` as `None` on fresh checkouts). `ValuesFile.load(path, config)` hands the raw document to `model_validate` — the selection fold into `choices`, the selection refusals, and the path-override vocabulary/ref-entry refusals ride pydantic validators, with `ConfigFile` arriving via validation context. Typed fields (`bundles` list, `path_overrides` map keyed by qualified name, `public_url`, `platform`) carve the consumed spellings; every other key is a slot value on the open `variables` map (ALL_CAPS enforced — `ENV_NAME_PATTERN`; keys carrying `LOCK_SLOT_MARKER` (`__LOCK__`) refuse — the lock fills those slots, never a hand). Path-override refusals (empty, URL-scheme, non-`stack/generated/compose/` layout) and the scheme-less `public_url` refusal are pydantic `ValidationError`s at parse; paths resolve in `up` against the repo root.
|
|
49
|
+
|
|
50
|
+
## Service derivation
|
|
51
|
+
|
|
52
|
+
- The Dockerfile-derived build set computes in `dockerfiles.py`'s `derive_services`, never authored: Dockerfile dirs one level below a top-level dir = services (spanning several top-level dirs refuses); context edges from bare-identifier `FROM`s naming a
|
|
53
|
+
sibling (stage names skipped); a literal `FROM` or slash-bearing `COPY --from=` refuses (base images ride as build args); `ARG <axis>` fans over the axis's declared values, dependents inherit the base's variants exactly (independent axes
|
|
54
|
+
over fanned bases, disagreeing parents, cycles refuse); `ARG {SERVICE}_SHA` declares the stamp receiver.
|
|
55
|
+
- `.dockerignore` = the allowlist the sha machinery hashes over a disk walk, staged per service into a temp git index (`git -c core.autocrlf=false update-index --add --stdin`, `git write-tree` minting the `tree-` OID); an empty service set returns `{}` without walking.
|
|
56
|
+
|
|
57
|
+
## Lock
|
|
58
|
+
|
|
59
|
+
- Format: top-level `platforms:` records the platform set the lock resolved for; `images:` name → `{index, platforms: {os/arch: reference}}` with every entry's platform keys equal to the top-level set; `charts:` name → flat digest-pinned reference; `bundles:` name → `{reference, requires}` — digest-pinned reference plus the
|
|
60
|
+
bundle's odd-parity `${NAME}` slots minus `:-`-defaulted ones, recorded at fresh resolution and recomputed from the pinned
|
|
61
|
+
artifact while the digest freezes (identical by content-addressing); `bundles:` keys are `{QUALIFIED_NAME}_BUNDLE` (qualified selection name upper-cased + `_BUNDLE`; a bare entry is its own name), arch-neutral; written only by the `lock` verb and
|
|
62
|
+
`build`'s incremental freeze (`relock` inline), over declared names only — built-image digests are unrepresentable, orphans prune by construction; never hand-authored.
|
|
63
|
+
- `LockFile.load(path, config)` = the only parser consumers use — config rides validation context (the `ValuesFile.load` precedent) and a missing lock reads as the empty lock paired against the config (`score` pre-gates with its own lock-required refusal); schema-only refusals fire on every parse (duplicate keys refuse naming the line; non-digest-pinned entries, cross-section name collisions reject; malformed or non-mapping documents surface raw pydantic/pyyaml errors; empty lock legal); the context-gated pairing validators refuse wholesale staleness naming the lock verb: recorded platforms ≠ `config.platforms`, an image entry's platform keys ≠ the top-level set, base_images/charts keys the lock lacks (coverage — bundles exempt, path overrides skip their entry), lock names the config no longer declares (orphans).
|
|
64
|
+
- `relock` (module-level in `lock.py`; reads the committed lock schema-only inline — drift and old formats are its input, never refused) computes per `base_images` entry the mirror-prefixed reference resolved into the index digest + one platform-manifest digest per declared platform (`crane manifest` + `crane digest`, crane always installed at relock entry; `crane copy` byte-identity holds both digests); `@digest`
|
|
65
|
+
declarations pass through prefixed-unresolved; declaration-incremental freeze: unchanged declarations copy forward, tag bump or platform-set change re-resolves, `--upgrade` re-resolves everything; charts freeze by
|
|
66
|
+
the same prefix match; bundle refs resolve direct — first-party, never mirror-prefixed — via `crane digest`, frozen by the same tag match.
|
|
67
|
+
- Upstream manifest selection (inline in `relock`) = a pure function of the declarations — the writing machine's arch never participates; exact `os`/`architecture` match; `unknown/unknown` + attestation entries skipped; a platform matching anything but exactly one manifest refuses.
|
|
68
|
+
- Projection selection law: `lock.index_references()` answers the lock's image index references (build edge); `lock.manifest_references(platform)` answers the concrete per-platform manifests (compose edge) and owns platform selection over the lock's recorded set — a recorded singleton self-selects, a multi-platform lock names `platform:` through `ValuesFile.platform` (missing/not recorded refuses listing the recorded platforms), never host introspection; the consumer's machine platform governs producer locks too; charts ride in both (single depth).
|
|
69
|
+
- The `lock` verb self-skips where no config or no declarations exist (print, exit 0 — CI's freshness step runs it unconditionally); `--check` asserts instead of writes (in-memory relock, exit 1, nothing touches the
|
|
70
|
+
tree).
|
|
71
|
+
|
|
72
|
+
## Mirror namespace
|
|
73
|
+
|
|
74
|
+
- Third-party images and charts are consumed through the org mirror namespace: upstream manifests get GC'd; CI = the single writer — engineers never push, and error text never teaches them to.
|
|
75
|
+
- Declarations are upstream-native; the prefixed form is derived; `mirrored_ref` = the single join; the `mirror` verb never enumerates bundle declarations.
|
|
76
|
+
- `up` never probes the registry — compose's include fetch and `--pull missing` surface missing digests, unclassified, at bring-up; a partial stack under `-d` is the accepted cost of no pre-flight probe.
|
|
77
|
+
- The `mirror` verb probes presence per reference over every recorded digest (`crane digest`; partially mirrored still triggers the copy; probe failure falls through to `crane copy`, the loud gate — probe errors never classified); one
|
|
78
|
+
`crane copy {ref} {prefix}/{ref}` per lacking reference copies the whole index; every declared `base_images`/charts reference is a target — nothing filtered; zero declared targets = a loud error; the
|
|
79
|
+
footer splits copied/present; crane's installer always runs so the pin, not a preexisting binary, decides the version.
|
|
80
|
+
- `build --push` leaves bake's `metadata.json` in the working directory — the handoff the toolkit's builds_ref step reads; boatyard reads no `REGISTRY` facts.
|
|
81
|
+
|
|
82
|
+
## Compose assembly and selection
|
|
83
|
+
|
|
84
|
+
- Selection law: `bundles:` names options only, by qualified `group-dash-option` spelling (a bare entry's name refuses as unselectable); ≤ one option per group; zero options leave the riders/routes carrying the assembly (omitting the auth pick = the anonymous edge) — an options-only
|
|
85
|
+
repo with an empty selection hands compose a name-only root, edge-only when a gateway is declared; unknown names refuse listing the qualified vocabulary; every selection refusal (unknown names, one-per-group) and the fold into `choices` (group → option) ride
|
|
86
|
+
`ValuesFile.load(root, config)` (the model's validators) on every values load (`up` always; `generate-score` when the file exists; `down` never). Membership law: selection consumption,
|
|
87
|
+
rendering, and staged inputs live inline in `verbs/up.py`; `compose.py` carries only the invocation spelling `compose_command` (both verbs); the up-only `render_env_entries`/`lock_env_unwind` live in `verbs/up.py`; nothing else lands in either.
|
|
88
|
+
- Leaf-service law: `ConfigFile.resolve_services(choices)` answers leaf services — the option's routes/ports ride before its group's (the auth option's route is `routes[0]`), its resolved service carries `auth: True`, an unselected group rides nothing; `up` consumes only these.
|
|
89
|
+
- Bundle overrides ride the values file's `path_overrides` map per machine (qualified name → a `stack/generated/compose/`-layout path, relative to the repo root); unknown names refuse listing
|
|
90
|
+
the vocabulary, unselected names are inert, and a name must resolve to a `ref:` entry — a local-entry name refuses listing the ref entries; an overridden ref skips its lock-entry requirement; the override carries the local-output teeth (exists, parses, no `edge` service, names and
|
|
91
|
+
requirements validated against the file) and contributes
|
|
92
|
+
the producer repo's config + paired lock — the nearest `boatyard.yml` above the override, then `ConfigFile.load` + the paired `LockFile.load`, projected via `manifest_references` under the consumer's platform — to the per-run env file under the entry prefix; no repo root above the override, or a drifted producer lock, refuses naming the remedy.
|
|
93
|
+
- The materialized root carries the project `name:`, the `include:` list (remote entries as `oci://` + the lock's `{NAME}_BUNDLE` digest ref — the yaml tag never rides an include; a selected ref lacking its entry refuses naming
|
|
94
|
+
`boatyard lock`; local outputs by absolute path; `services:` declaration order), the emitted edge (iff a gateway is declared — routes or not — attached to every selected bundle's `{entry}-net` network), parent-file port stanzas; compose
|
|
95
|
+
merges parent ports into included services and honors `!reset` — the merge needs compose ≥ 2.40.
|
|
96
|
+
- Namespacing law: score renders every bundle resource under `{entry}-` (services, configs keys) on a per-bundle network carrying the bare name as a service alias — include collisions are structurally impossible,
|
|
97
|
+
rendered hostnames still resolve, and accidental cross-bundle DNS fails instead of silently resolving: a bundle references only its own services; cross-bundle coupling rides the consumer's declared surface
|
|
98
|
+
(env slots, route upstreams). `up` composes `{entry}-` onto authored route upstreams and port owners; `boatyard.yml` stays bare. Local-output lock-key slots (the lock's key set draws the line)
|
|
99
|
+
rewrite under `env_lock_slot(entry, key)` — the `{ENTRY}__LOCK__KEY` spelling, the `LOCK_SLOT_MARKER` infix reserving lock-filled slots against the values file (whose parse refuses the marker); consumer-surface slots (edge image is baked, gateway/ports, secrets, consumer-interpolated files) stay bare; publish fans
|
|
100
|
+
its bake substitution keys with the same spelling mechanically.
|
|
101
|
+
- Validation teeth: relock reads every declared bundle's content inline (the compose files ride as inline base64 layer data in `crane manifest`, `crane blob` the
|
|
102
|
+
fallback) and refuses any namespaced `{entry}-` route upstream or port owner that resolves to no service in the bundle; `up` applies the same refusal to local outputs and path overrides by scanning the local
|
|
103
|
+
file; `up` refuses a selected bundle whose recorded `requires` no environment source fills (boatyard.values.yaml or the
|
|
104
|
+
lock env file; lock-key slots are satisfied by construction; a shell-set unfilled name gains a "set in the shell and ignored" clause); a selected bundle output that does not exist refuses naming generate-score; `up` never invokes `docker compose config`.
|
|
105
|
+
- Edge stanza: `image:` carries the lock's platform manifest for `CADDY_IMAGE` baked at assembly (the config-declared key is coverage-guaranteed in the lock), the Caddyfile configs entry, `gateway:` as published port, `FORWARDED_PROTO`/`FORWARDED_PORT` derived from `public_url` under a selected auth option (refusing when absent); the
|
|
106
|
+
edge does no token validation — re-protection is a bundle-shipped oauth2-proxy sidecar treaty; the service name `edge` is reserved.
|
|
107
|
+
- Caddyfile: one site on internal `:8443`; `handle` blocks mutually exclusive (caddy sorts longest-path-first, declaration order cannot shadow); `handle_path` for `strip_prefix`; `header_up X-Forwarded-*` only on the auth route; `raw:`
|
|
108
|
+
content rides verbatim (escape hatch, review flag); the whole file rides inline as `configs.content` with every `$` doubled.
|
|
109
|
+
- Per-run files: lifetimes ride the caller-owned `ExitStack`, never an exception handler; the handoff execs — a successful run never unwinds, the files ride out in the OS temp dir; on failure the compose files unlink, the env file
|
|
110
|
+
keeps with a path-line-only echo (`lock_env_unwind` — its contents may carry secrets); `up` writes ONE per-run dotenv inline (`render_env_entries` from `verbs/up.py`): values then lock entries under `#` source headers — the lock section carries only `{ENTRY}__LOCK__KEY` fans (no bare consumer entries; the edge bakes its image), one per selected local entry from the consumer lock and one per path override from its producer lock (producer
|
|
111
|
+
pins win; a remote-only selection attaches none); the values parse's marker refusal is the collision law — no merge check exists;
|
|
112
|
+
`compose_command` = the one docker compose invocation spelling, prefixed `env -u K1 -u K2 …` (sorted) when `slot_names` is non-empty — the scrub defeating compose's shell-beats-env-file precedence; `up` passes values ∪ lock entries ∪ requirement names; `down` reads no values file, no lock, no selection — a name-only root + `--remove-orphans` removes
|
|
113
|
+
every container the project ever created; a config-less repo refuses at load and a `services:`-less repo refuses on `up` only.
|
|
114
|
+
|
|
115
|
+
## Build
|
|
116
|
+
|
|
117
|
+
- `run_build` renders the deterministic per-run bake file (authored key order, byte-identical for identical configs; bake validates its own input — no document model); it takes the already-loaded `config` — the build verb owns its flag refusals; a failed bake re-raises with the rendered content attached.
|
|
118
|
+
- Push axis: `--push` (bool) pushes for the config's `repository`; `--push-to <addr>` re-roots tags + cache refs under that registry; the flags together refuse; an address with a scheme, trailing slash, or host
|
|
119
|
+
carrying neither `.` nor `:` refuses; `--push` without a declared `repository` refuses.
|
|
120
|
+
- `host_platform()` = the one host fact left: the flagless default set is every service's first-declared variant restricted to this machine's arch, and an empty default set refuses naming the arch + `--targets` (bake with no target arguments
|
|
121
|
+
builds every service, silently defeating the selection); `--targets` spelling is shape-dictated — bare `name` for an axis-less service, `name/variant` for an axis service (a bare name on an axis service refuses naming its variants; the dashed
|
|
122
|
+
target name is bake-internal and refuses; repeated addresses ride through — bake owns set semantics); unknown names/variants refuse listing the vocabulary.
|
|
123
|
+
- Per target: dockerfile by convention; platforms collapse to the host arch in load mode (a single declared platform rides through); args — every `base_images` key as `${KEY:-}` on every service, the axis
|
|
124
|
+
ARG at its variant value, `{SERVICE}_SHA` as `${…:?err}`; tags `{namespace}/{target}:latest` + `:${SERVICE_SHA}`; `target:`-prefixed `additional_contexts`; registry cache refs under `{namespace}/build-cache:{target}` in push mode.
|
|
125
|
+
- The incremental lock write rides every building run (the `uv sync` shape — anyone who builds also locks), landing only after target selection + its refusals; bake reads the fresh entries as environment (index form); a building run with
|
|
126
|
+
zero derived services refuses naming the lock verb.
|
|
127
|
+
- `metadata.json`: cwd-stable, unlinked before the run, coverage-checked against the requested targets after; local default-target builds run the daemon
|
|
128
|
+
GC-limit check (`MIN_KEEP_STORAGE_GB` = 60; absent/null `defaultKeepStorage` refuses with a sample snippet, an explicit zero is a set value refusing below the floor; no readable daemon.json warns and continues).
|
|
129
|
+
|
|
130
|
+
## Score generation
|
|
131
|
+
|
|
132
|
+
- Deterministic generation from `stack/score/` into `stack/generated/`; one compose rendering per local option + bare entry (output `stack/generated/compose/compose.{name}.yaml`; the compose state dir is re-created per entry); k8s = one
|
|
133
|
+
monolithic rendering over `k8s.workloads` + `k8s.vars`, untouched by `bundles:`, with instance names deterministic by construction — state.yaml pre-seeds `InstanceSuffix` = `-` + first 10 hex of sha256(metadata.name) per workload between init
|
|
134
|
+
and generate (state-format coupling bounded by the pinned tool version); `--local` writes the gitignored local output with the local storage class; charts render through `helm template` against the
|
|
135
|
+
lock's `{NAME}_CHART` entry (a chart without its lock entry refuses naming the lock verb; helm is a renderer, never a deployer); `stack/score/` stays read-only; output reproduces from a fresh clone.
|
|
136
|
+
- Substrate law: compose renderings consume the lock's platform manifests (the artifact records the exact bytes one machine runs); k8s renderings consume the index form (kubelet resolves per node).
|
|
137
|
+
- `ensure_tools` installs the three pinned tools into `/usr/local/bin`: version-probe each, fetch on absence or pin mismatch.
|
|
138
|
+
- `--check` = the CI staleness gate: self-skips (exit 0) where nothing generates; otherwise regenerates in-process; any `git status` change over the outputs exits 1 printing the diff; `--check` + `--local` are mutually
|
|
139
|
+
exclusive.
|
|
140
|
+
- Post-passes on compose output are typed transformations on `ComposeDocument`, dumped exactly once — pydantic's declared-fields-first order is the artifacts' deterministic key order: a workload-major pass collapses `{workload}-{container}` services to the container name and inlines every `type: bind` volume as a top-level
|
|
141
|
+
`configs.content` entry — zero host-filesystem references in committed artifacts; the `$`-doubling exception rides the owning workload's consumer-interpolated declaration; workload-less services ride through under
|
|
142
|
+
their rendered names; the barrier pins the `ALPINE_IMAGE` lock slot, namespacing like every service (`{entry}-wait-for-resources`, `depends_on` rewritten per rendered dependency) and every lock-key slot under the
|
|
143
|
+
entry prefix; each collapsed service name is
|
|
144
|
+
claimed once (collision loud); the `{entry}-` namespace pass covers services, configs keys, and the per-bundle network with bare aliases.
|
|
145
|
+
- The image lock is required and non-empty; the compose target requires the `ALPINE_IMAGE` entry.
|
|
146
|
+
- `$`-doubling law, stated once: a literal `$` doubles whenever file text rides inside a compose document (edge Caddyfile, publish's file-sourced configs, score's bind inlining); text outside a compose document (k8s-side inclusion, k8s manifests) never doubles.
|
|
147
|
+
- `docker-build-matrix` leg schema (`targets`, `variant`) = a named contract with the binding — renaming a key breaks its fanout YAML; one partition per closure × variant; `targets` carries `--targets` addresses (bare `name` / `name/variant`), never dashed target names.
|
|
148
|
+
|
|
149
|
+
## Provisioners and markers
|
|
150
|
+
|
|
151
|
+
- Authored provisioner files carry `state`/`outputs`/`volumes`/`services` as real YAML mappings + `manifests` as a real list — score's string form is generated, never authored; a field already in string form = a loud validation error; `extra="forbid"`.
|
|
152
|
+
- Compilation resolves include markers, then lock markers, both before validation; the compiled form lives in a per-run tempdir under `score_dir`, never the committed tree; `--provisioners` paths stay POSIX-relative;
|
|
153
|
+
authored key order controls dump order at every level; comments are dropped; registration derives by resource-type closure in sorted-filename order;
|
|
154
|
+
orphan provisioner files silently no-ship.
|
|
155
|
+
- Marker grammar: a single-key mapping (authored sources stay plain-YAML-parseable); `include:` inserts bytes verbatim, `include-yaml:` inserts parsed structure; neither transforms content (`${VAR}` slots pass through; `$`-handling is
|
|
156
|
+
per-consumer); paths anchor at the repo root — absolute paths, `..` escapes, missing files, cycles = loud errors; text content is never scanned for markers; `lock: NAME`/`lock-image: NAME` substitute the full reference /
|
|
157
|
+
registry/repository/tag/digest (grafana's `digest_field: sha`), scoped to provisioner compiles and chart values (k8s provisioners have no env file); workloads use `${NAME}` slots; a literal `image:` (no `${`/`{{`
|
|
158
|
+
slot) refuses at every marker resolution — provisioner compiles and every chart values file, markers or not — and at the workload lens.
|
|
159
|
+
|
|
160
|
+
## Secrets
|
|
161
|
+
|
|
162
|
+
- `stack/secrets.yaml` is required (missing refuses at generate-score; an empty file = the empty declaration) — `KEY: description` or `KEY: {description, mint: true}` (string specs coerce; keys ALL_CAPS); each rendering's consumed keys validate as a subset (pair-variance legal);
|
|
163
|
+
the cluster Secret derives `{project_name}-secrets`; k8s consumption = `secretKeyRef` naming it (`envFrom`/`extraEnvFrom` `secretRef`s impose no per-key constraint); compose consumption = `${ALL_CAPS}` brace slots with odd `$`-parity (`$$`-escaped
|
|
164
|
+
`configs.content` doesn't count; lock-key slots don't count — the per-run env file fills them); the mint list artifact (`stack/generated/secrets.txt`) generates from the declaration alone.
|
|
165
|
+
|
|
166
|
+
## Publish
|
|
167
|
+
|
|
168
|
+
- Publishing happens iff local entries are declared — no flag, no section; artifact names derive from `repository` (`{repository}/{qualified-or-entry-name}`); `--plan` prints the entries as a bare JSON array (`[]` when none), no
|
|
169
|
+
login, no publish — the CI getter's derivation source; bake substitutes `${ALL_CAPS}` slots against `service_shas()` tree-tags + the lock's index projection, the lock keys fanned under the bundle prefix for the
|
|
170
|
+
artifact's namespaced spellings; unresolved vars stay slots in the artifact for the consumer's values file
|
|
171
|
+
(non-empty stubs exist only so publish's interpolation validation passes); authored `configs.content` rides verbatim (single-`$` slots = the include consumer's interpolation surface — `$`-doubling is score's concern, not publish's); image
|
|
172
|
+
refs under `{repository}/` pin to `@sha256` via memoized `crane digest`; each entry publishes once under the `<sha>` tag, aliasing `<branch>` via `crane tag` (compose stamps wall-clock `created` — only the alias makes both tags name one digest); registry login reads `REGISTRY_USERNAME`/`REGISTRY_TOKEN`.
|
|
173
|
+
|
|
174
|
+
## Deployment-surface annotations
|
|
175
|
+
|
|
176
|
+
- Everything the substrate needs that score's container model doesn't carry lives in the workload file as `outernet.internal/*` annotations — `boatyard.yml` never carries a per-workload runtime fact; the shipped patch templates
|
|
177
|
+
(package data in `src/boatyard/score_patch_templates/`) are the annotation reader — one annotation, one template, one behavior; consumer repos never author one; refusal laws live in boatyard code (templates are data and cannot
|
|
178
|
+
refuse); the lens is `WorkloadDocument`/`ComposeWorkloadDocument` (`metadata.name` pinned to the workload file stem; the compose lens adds httpGet-requires-probe-adapter), one spelling `WorkloadDocument.parse(raw, file_name)`;
|
|
179
|
+
score parses workloads once and propagates `metadata.annotations` into both renderings.
|
|
180
|
+
|
|
181
|
+
| Annotation | Behavior |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `outernet.internal/gpu` | cuda/rocm compose pair registers per `GPU_VARIANT`; `gpu-pod.tpl` on every k8s init; nodeSelector `placeframe.io/gpu`; runtime vocabulary, distinct from build-time variant axes |
|
|
184
|
+
| `outernet.internal/restart` | compose enum (`no`/`always`/`on-failure`/`unless-stopped`); outside refuses |
|
|
185
|
+
| `outernet.internal/probe-adapter` | `python-urllib` or `bash-http`; an httpGet probe without the annotation refuses loudly — score-compose silently drops httpGet |
|
|
186
|
+
| `outernet.internal/consumer-interpolated` | declared `files:` names ride un-doubled; a name matching no declared file refuses |
|
|
187
|
+
|
|
188
|
+
## This file's rules
|
|
189
|
+
|
|
190
|
+
The only reader is a coding agent: the code is the source of truth for mechanism; this file carries only what an agent would otherwise get wrong.
|
|
191
|
+
|
|
192
|
+
- Budget: 200 lines and 26KB, both hard — an addition crossing either cap cuts something else here; admit a line only if removing it would cause a mistake.
|
|
193
|
+
- One law, one home: search before adding; an existing law updates in place, never a second copy. The module map carries one line per module; laws live in sections named for the concern.
|
|
194
|
+
- Identifiers, flags, file paths, and refusal phrasings stay exact — they are what agents grep; no line exceeds 300 characters.
|
|
195
|
+
- A code change that invalidates a line here is a doc change in the same commit; a fact verifiable only against another repo or CI is a pointer to that owner, never restated here.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright 2026 Tyler Hatch
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: boatyard
|
|
3
|
+
Version: 0.6.0.dev170
|
|
4
|
+
Summary: Deploy-files definition language and compiler, plus a runtime for the compose target
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
License-File: NOTICE
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: bashrun>=0.1.0
|
|
9
|
+
Requires-Dist: packaging>=24.1
|
|
10
|
+
Requires-Dist: pydantic>=2.11.7
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Requires-Dist: typer>=0.17.4
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# boatyard
|
|
2
|
+
|
|
3
|
+
A deploy-files definition language and compiler, plus a runtime for the compose target — lifecycle verbs (`boatyard up`, `down`, `build`, `lock`, `mirror`, `publish-compose`, `generate-score`) driven by a per-repo `boatyard.yml`. Agent-facing documentation lives in [`AGENTS.md`](./AGENTS.md).
|