foundry-implementation-actor 0.5.0__tar.gz → 0.6.0__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.
- foundry_implementation_actor-0.6.0/.github/workflows/release.yml +210 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/CLAUDE.md +23 -5
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/PKG-INFO +56 -3
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/README.md +55 -2
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/ADR-FIA-0004-the-three-amigos-round.md +12 -3
- foundry_implementation_actor-0.6.0/adr/ADR-FIA-0006-the-image-is-published-to-two-registries.md +104 -0
- foundry_implementation_actor-0.6.0/adr/ADR-FIA-0007-a-sessions-budget-is-an-environment-setting.md +105 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/README.md +2 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/docker/Dockerfile +6 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/examples/ACME.PARTS.CAP.SUP.007.WID-implementation/Dockerfile +8 -1
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/examples/README.md +18 -1
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/pyproject.toml +1 -1
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/__init__.py +7 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/cli.py +6 -1
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/engine.py +54 -16
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/serve.py +15 -2
- foundry_implementation_actor-0.6.0/src/foundry_implementation_actor/settings.py +102 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_cli.py +18 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_engine.py +129 -2
- foundry_implementation_actor-0.6.0/tests/test_settings.py +107 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/uv.lock +1 -1
- foundry_implementation_actor-0.5.0/.github/workflows/release.yml +0 -106
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/.github/workflows/ci.yml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/.gitignore +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/ADR-FIA-0001-the-machinery-leaves-the-capability.md +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/ADR-FIA-0002-testing-leaves-this-actors-contract.md +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/ADR-FIA-0003-the-dev-test-agreement-has-no-home-yet.md +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/ADR-FIA-0005-the-actor-ships-an-image.md +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/adr/template.md +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/examples/ACME.PARTS.CAP.SUP.007.WID-implementation/actor-agentic-context.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/scripts/probe_grounding.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/cards/actor-data.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/cards/actor-message.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/cards/actor-synchronous-messaging.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/cards/actor.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/config.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/conformance.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/correlation.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/grounding.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/handler.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/instance.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/src/foundry_implementation_actor/schemas/agentic-context.schema.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/conftest.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/fixtures/broken/actor-agentic-context.yaml +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_cards.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_config.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_conformance.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_grounding.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_handler.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_instance.py +0 -0
- {foundry_implementation_actor-0.5.0 → foundry_implementation_actor-0.6.0}/tests/test_portability.py +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ["v*"]
|
|
5
|
+
|
|
6
|
+
# A REGISTRY ADDED AFTER A TAG WAS ALREADY CUT. The image job below publishes to two registries,
|
|
7
|
+
# and the second one was not always there. Running this workflow by hand backfills the image for
|
|
8
|
+
# a version whose wheel is already on PyPI — without inventing a version number whose only
|
|
9
|
+
# content is "the last release missed a registry".
|
|
10
|
+
#
|
|
11
|
+
# THE TAG IS AN INPUT, NOT THE REF THIS RUNS FROM. GitHub takes the workflow FILE from the ref it
|
|
12
|
+
# is dispatched on, and the tag needing a backfill is by definition older than the workflow that
|
|
13
|
+
# can do it — dispatching on that tag would run the very file that lacks this trigger. So run it
|
|
14
|
+
# from the default branch and name the tag here; the checkout below takes the CONTENT from the
|
|
15
|
+
# tag, and the version gate still refuses a tag that disagrees with the `pyproject.toml` beside
|
|
16
|
+
# it.
|
|
17
|
+
workflow_dispatch:
|
|
18
|
+
inputs:
|
|
19
|
+
tag:
|
|
20
|
+
description: "Existing tag to publish an image for, e.g. v0.5.0"
|
|
21
|
+
required: true
|
|
22
|
+
|
|
23
|
+
# PyPI Trusted Publishing (OIDC) — no token is stored anywhere. The one-time setup is a pending
|
|
24
|
+
# publisher on pypi.org naming this repo and this workflow; after the first release it becomes a
|
|
25
|
+
# normal publisher. See README.
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write # PyPI Trusted Publishing
|
|
28
|
+
contents: read
|
|
29
|
+
packages: write # the image, to this repo's own container registry
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
publish:
|
|
33
|
+
# ONLY ON A TAG PUSH. A manual run exists to backfill an IMAGE (see `on:` above), and PyPI
|
|
34
|
+
# refuses a version it already holds — so re-uploading is not merely redundant, it fails, and
|
|
35
|
+
# would take the image job down with it.
|
|
36
|
+
if: github.event_name == 'push'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- uses: astral-sh/setup-uv@v5
|
|
42
|
+
|
|
43
|
+
- name: build
|
|
44
|
+
# No fetch step and no external token: the contract is committed here. A release depends
|
|
45
|
+
# on nothing but this checkout and PyPI.
|
|
46
|
+
run: uv build
|
|
47
|
+
|
|
48
|
+
- name: the wheel must carry its contract
|
|
49
|
+
run: |
|
|
50
|
+
uv venv /tmp/probe
|
|
51
|
+
uv pip install --python /tmp/probe/bin/python -q dist/*.whl
|
|
52
|
+
/tmp/probe/bin/foundry-implementation-actor lint examples/ACME.PARTS.CAP.SUP.007.WID-implementation
|
|
53
|
+
|
|
54
|
+
- name: the wheel must carry the actor's own cards
|
|
55
|
+
# Same reason as the line above, for the other half of what this package ships. The
|
|
56
|
+
# `-actor` suffix asserts a papeete-actor underneath (ADR-ECO-0022); a wheel published
|
|
57
|
+
# without its cards makes the name a claim the artifact cannot honour, and a published
|
|
58
|
+
# version is not something to discover that from.
|
|
59
|
+
run: |
|
|
60
|
+
/tmp/probe/bin/papeete-actor-synchronous-messaging lint-card \
|
|
61
|
+
"$(/tmp/probe/bin/python -c 'from foundry_implementation_actor import cards_path; print(cards_path())')"
|
|
62
|
+
|
|
63
|
+
- name: grounding renders a CLAUDE.md whose imports resolve
|
|
64
|
+
# BEFORE the upload, not after. A wheel that ships without its schema, or whose grounding
|
|
65
|
+
# emits an @-import to a file it never wrote, produces sessions grounded in nothing that
|
|
66
|
+
# look identical to correct ones. That is not something to discover from a published
|
|
67
|
+
# version.
|
|
68
|
+
run: /tmp/probe/bin/python scripts/probe_grounding.py examples/ACME.PARTS.CAP.SUP.007.WID-implementation
|
|
69
|
+
|
|
70
|
+
- name: publish to PyPI
|
|
71
|
+
run: uv publish --trusted-publishing always
|
|
72
|
+
|
|
73
|
+
# ── the image, from the same tag and the same checkout ────────────────────────────────────────
|
|
74
|
+
#
|
|
75
|
+
# TWO ARTIFACTS, ONE RELEASE (ADR-FIA-0005). A use pins an image; an embedder pins a wheel; both
|
|
76
|
+
# are this package at this version. A tag that produced one and not the other leaves a consumer
|
|
77
|
+
# unable to take the release at all — so this job builds the wheel again from the same checkout
|
|
78
|
+
# rather than installing the one just uploaded: the image must not depend on PyPI having already
|
|
79
|
+
# indexed it, and must not be able to pick up a different build of the same version.
|
|
80
|
+
#
|
|
81
|
+
# It runs AFTER the wheel is published, not beside it. A failed upload should not leave an image
|
|
82
|
+
# published for a version that does not exist on PyPI; the reverse — a wheel with the image still
|
|
83
|
+
# to come — is recoverable by re-running this job alone.
|
|
84
|
+
image:
|
|
85
|
+
needs: publish
|
|
86
|
+
# `needs` still orders this after the wheel on a tag push. On a manual backfill `publish` is
|
|
87
|
+
# skipped, and a skipped dependency skips its dependents too unless `always()` says otherwise —
|
|
88
|
+
# so the condition has to name both cases rather than relying on `needs` alone.
|
|
89
|
+
if: always() && (needs.publish.result == 'success' || github.event_name == 'workflow_dispatch')
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v4
|
|
93
|
+
with:
|
|
94
|
+
# Empty on a tag push, which is `checkout`'s own default — the ref that triggered the
|
|
95
|
+
# run. On a manual backfill it is the tag being republished, so every step below reads
|
|
96
|
+
# that tag's own tree rather than whatever the default branch has moved on to.
|
|
97
|
+
ref: ${{ inputs.tag }}
|
|
98
|
+
|
|
99
|
+
- uses: astral-sh/setup-uv@v5
|
|
100
|
+
|
|
101
|
+
- name: the version is the tag
|
|
102
|
+
# The tag is what a consumer writes in a FROM line. A tag that disagrees with
|
|
103
|
+
# pyproject.toml would publish an image whose own `version()` contradicts its name, which
|
|
104
|
+
# is exactly the confusion the CI gate above exists to prevent for the wheel. On a backfill
|
|
105
|
+
# this compares the named tag against the `pyproject.toml` checked out FROM that tag, so a
|
|
106
|
+
# typo in the input is refused rather than published under the wrong name.
|
|
107
|
+
id: version
|
|
108
|
+
env:
|
|
109
|
+
TAG: ${{ inputs.tag || github.ref_name }}
|
|
110
|
+
run: |
|
|
111
|
+
tag="${TAG#v}"
|
|
112
|
+
declared="$(uv run --quiet python -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
113
|
+
[ "$tag" = "$declared" ] || { echo "::error::tag $TAG does not match pyproject version $declared"; exit 1; }
|
|
114
|
+
echo "version=$tag" >> "$GITHUB_OUTPUT"
|
|
115
|
+
|
|
116
|
+
- name: build
|
|
117
|
+
run: uv build
|
|
118
|
+
|
|
119
|
+
- uses: docker/login-action@v3
|
|
120
|
+
with:
|
|
121
|
+
registry: ghcr.io
|
|
122
|
+
username: ${{ github.actor }}
|
|
123
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
124
|
+
|
|
125
|
+
- name: the image, tagged and latest
|
|
126
|
+
env:
|
|
127
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
128
|
+
# `latest` FOLLOWS THE RELEASE STREAM, NOT A BACKFILL. A manual run exists to give an
|
|
129
|
+
# already-released version an image in a registry it missed; run against an older tag it
|
|
130
|
+
# would otherwise drag `latest` backwards onto it, silently, in whichever registry a
|
|
131
|
+
# consumer happens to pull from. The version tag is the whole point of a backfill —
|
|
132
|
+
# `latest` already names something newer, and stays there.
|
|
133
|
+
MOVE_LATEST: ${{ github.event_name == 'push' }}
|
|
134
|
+
run: |
|
|
135
|
+
image="ghcr.io/${{ github.repository }}"
|
|
136
|
+
docker build -f docker/Dockerfile -t "$image:$VERSION" -t "$image:latest" .
|
|
137
|
+
docker push "$image:$VERSION"
|
|
138
|
+
if [ "$MOVE_LATEST" = "true" ]; then docker push "$image:latest"; fi
|
|
139
|
+
|
|
140
|
+
# ── the product's own registry ────────────────────────────────────────────────────────────
|
|
141
|
+
#
|
|
142
|
+
# TWO REGISTRIES, ONE IMAGE. GHCR is where this package publishes; a product that RUNS uses
|
|
143
|
+
# of it pulls from its own registry, which its cluster already authenticates to and which a
|
|
144
|
+
# `FROM` line inside that cluster's builder can reach without a second credential. The base
|
|
145
|
+
# image has to exist in both, because the two are reached by different things: an embedder
|
|
146
|
+
# writing a Dockerfile reads the README and pins GHCR, while a use built by an in-cluster
|
|
147
|
+
# builder resolves its `FROM` against the registry that builder has a push token for.
|
|
148
|
+
#
|
|
149
|
+
# THE SAME IMAGE, NOT ANOTHER BUILD. `docker tag` re-points the build the step above already
|
|
150
|
+
# pushed, so both registries hold one digest. Building a second time would let them diverge —
|
|
151
|
+
# a base-image refresh between two steps of one job is enough — and nothing downstream would
|
|
152
|
+
# ever notice that `:0.5.0` meant two different things depending on where you pulled it.
|
|
153
|
+
#
|
|
154
|
+
# ONE VARIABLE NAMES THE WHOLE TARGET. `vars.PRODUCT_IMAGE` is the full repository path, e.g.
|
|
155
|
+
# `<name>.azurecr.io/<product>/foundry-implementation-actor`; the login server is its first
|
|
156
|
+
# segment. This package is generic across capabilities and names no product of its own — the
|
|
157
|
+
# same rule `tests/test_portability.py` enforces over `src/` — so the product that hosts a
|
|
158
|
+
# registry names itself in a repository variable rather than in this file.
|
|
159
|
+
#
|
|
160
|
+
# UNSET IS A VALID STATE, AND IT SAYS SO. A fork, or this repo before its credentials were
|
|
161
|
+
# added, publishes to GHCR alone; that is a `::notice`, not a failure. Configured-but-broken
|
|
162
|
+
# IS a failure — the push below is not best-effort, because an image the product's cluster
|
|
163
|
+
# cannot pull is the whole reason this block exists.
|
|
164
|
+
- name: is a product registry configured?
|
|
165
|
+
id: product
|
|
166
|
+
env:
|
|
167
|
+
PRODUCT_IMAGE: ${{ vars.PRODUCT_IMAGE }}
|
|
168
|
+
run: |
|
|
169
|
+
if [ -z "$PRODUCT_IMAGE" ]; then
|
|
170
|
+
echo "::notice::PRODUCT_IMAGE is unset — publishing to GHCR only"
|
|
171
|
+
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
172
|
+
exit 0
|
|
173
|
+
fi
|
|
174
|
+
case "$PRODUCT_IMAGE" in
|
|
175
|
+
*/*/*) : ;;
|
|
176
|
+
*) echo "::error::PRODUCT_IMAGE must be <registry>/<path>/<repository>, got $PRODUCT_IMAGE"; exit 1 ;;
|
|
177
|
+
esac
|
|
178
|
+
echo "server=${PRODUCT_IMAGE%%/*}" >> "$GITHUB_OUTPUT"
|
|
179
|
+
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
180
|
+
|
|
181
|
+
- uses: docker/login-action@v3
|
|
182
|
+
if: steps.product.outputs.configured == 'true'
|
|
183
|
+
with:
|
|
184
|
+
registry: ${{ steps.product.outputs.server }}
|
|
185
|
+
username: ${{ secrets.ACR_PUSH_USERNAME }}
|
|
186
|
+
password: ${{ secrets.ACR_PUSH_PASSWORD }}
|
|
187
|
+
|
|
188
|
+
- name: the same image, in the product's registry
|
|
189
|
+
if: steps.product.outputs.configured == 'true'
|
|
190
|
+
env:
|
|
191
|
+
PRODUCT_IMAGE: ${{ vars.PRODUCT_IMAGE }}
|
|
192
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
193
|
+
MOVE_LATEST: ${{ github.event_name == 'push' }}
|
|
194
|
+
run: |
|
|
195
|
+
ghcr="ghcr.io/${{ github.repository }}"
|
|
196
|
+
docker tag "$ghcr:$VERSION" "$PRODUCT_IMAGE:$VERSION"
|
|
197
|
+
docker push "$PRODUCT_IMAGE:$VERSION"
|
|
198
|
+
if [ "$MOVE_LATEST" = "true" ]; then
|
|
199
|
+
docker tag "$ghcr:latest" "$PRODUCT_IMAGE:latest"
|
|
200
|
+
docker push "$PRODUCT_IMAGE:latest"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
- name: a use built on it is still an actor
|
|
204
|
+
# BEFORE the image is something anyone can pin — the same reason the grounding probe runs
|
|
205
|
+
# before the upload above. An image that builds but produces a use that does not conform is
|
|
206
|
+
# not something to discover from a published tag.
|
|
207
|
+
run: |
|
|
208
|
+
docker build --build-arg ACTOR_IMAGE=ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }} \
|
|
209
|
+
-t use:release examples/ACME.PARTS.CAP.SUP.007.WID-implementation
|
|
210
|
+
docker run --rm use:release foundry-implementation-actor lint /actor
|
|
@@ -29,7 +29,7 @@ There is no separate lint/format command configured in this repo.
|
|
|
29
29
|
|
|
30
30
|
## Architecture
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Eight modules under `src/foundry_implementation_actor/`:
|
|
33
33
|
|
|
34
34
|
- **`config.py`** — `CapabilityConfig`. The heart. Loads the sidecar and derives **every**
|
|
35
35
|
rendering of the capability id from two declared fields (`capability`, `source_repo`). Also
|
|
@@ -48,6 +48,10 @@ Seven modules under `src/foundry_implementation_actor/`:
|
|
|
48
48
|
the derived wire contract only (door ids, each door's request/completion schema and engine).
|
|
49
49
|
Prose and `actor.yaml`'s `name:` are deliberately not compared — a use should name its own
|
|
50
50
|
capability. `lint` runs it beside the sidecar gate.
|
|
51
|
+
- **`settings.py`** — `Settings`. How much a session may spend: turns and seconds, per door, as
|
|
52
|
+
one `field → environment variable` table with a `from_env` that refuses a value it cannot read.
|
|
53
|
+
`serve` fills the engine from it (ADR-FIA-0007). The defaults live here, not in `engine.py`,
|
|
54
|
+
because `engine.py` names the variable when a budget runs out.
|
|
51
55
|
- **`cli.py`** — argparse wiring only, no logic of its own.
|
|
52
56
|
|
|
53
57
|
Beside them, two folders of committed contract, both shipped in the wheel:
|
|
@@ -88,6 +92,15 @@ Beside them, two folders of committed contract, both shipped in the wheel:
|
|
|
88
92
|
- **The clone is full, never `--depth 1`.** `papeete_version.compute()` runs `git describe --tags`
|
|
89
93
|
against it and needs the matching tag's commit reachable.
|
|
90
94
|
- **The generated `CLAUDE.md` appends** to one the repo already commits. Never overwrite.
|
|
95
|
+
- **The image is one build in two registries.** GHCR is where this package publishes; a product's
|
|
96
|
+
own registry is where the in-cluster builder that resolves a use's `FROM` line can actually
|
|
97
|
+
authenticate (ADR-FIA-0006). `release.yml` `docker tag`s one build into both so they hold one
|
|
98
|
+
digest — never add a second `docker build`, and never hardcode a registry: the product names
|
|
99
|
+
itself in `vars.PRODUCT_IMAGE`, the same reason `src/` names no capability.
|
|
100
|
+
- **A budget is an environment setting, and a door that runs out says what to change.** Turns and
|
|
101
|
+
timeouts are `Settings` fields with an `ENV` entry each, not constructor defaults `serve` never
|
|
102
|
+
passes (ADR-FIA-0007). Never add a knob without an entry in `ENV` and `ENGINE_KWARGS` — and never
|
|
103
|
+
put one in the sidecar: a capability declares what it is, not how long its actor may think.
|
|
91
104
|
- **Never set `ANTHROPIC_API_KEY` in a container running this.** In `claude -p` non-interactive
|
|
92
105
|
mode an API key in the environment is always preferred over `CLAUDE_CODE_OAUTH_TOKEN`, silently
|
|
93
106
|
routing every session through metered billing. There is no warning; the only symptom is the bill.
|
|
@@ -104,7 +117,12 @@ similar weight rather than only writing it into code comments.
|
|
|
104
117
|
## Releasing
|
|
105
118
|
|
|
106
119
|
Tag-triggered (`v*`) via `.github/workflows/release.yml`, publishing to PyPI through Trusted
|
|
107
|
-
Publishing (OIDC) — no stored token. The
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
Publishing (OIDC) — no stored token. The image goes to GHCR and, when `vars.PRODUCT_IMAGE` names
|
|
121
|
+
one, to a product's own registry as well (ADR-FIA-0006). The workflow also accepts a manual run
|
|
122
|
+
that takes a tag as an INPUT — dispatched from the default branch, because the file comes from the
|
|
123
|
+
dispatched ref and a tag needing a backfill predates the workflow that can do it — to give an
|
|
124
|
+
already-released version an image in a registry it missed; that run skips PyPI and does not move
|
|
125
|
+
`latest`. The release job builds the wheel, installs it into
|
|
126
|
+
a throwaway venv, and renders a `CLAUDE.md` from a fixture sidecar, asserting its `@`-imports
|
|
127
|
+
resolve, before publishing. `ci.yml` runs the suite plus two gates — *the wheel must carry its
|
|
128
|
+
contract* and *the gate must run* — and reaches nothing outside its own checkout.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: foundry-implementation-actor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Runs a headless Claude Code implementation session against one capability's own repo — a papeete-actor for one use, with the capability supplied by a sidecar.
|
|
5
5
|
Project-URL: Homepage, https://github.com/papeete-hub/foundry-implementation-actor
|
|
6
6
|
Author-email: Papeete Consulting <yoann.remy@outlook.com>
|
|
@@ -70,7 +70,7 @@ ground_in:
|
|
|
70
70
|
and, beside it, a four-line Dockerfile:
|
|
71
71
|
|
|
72
72
|
```dockerfile
|
|
73
|
-
FROM ghcr.io/papeete-hub/foundry-implementation-actor:0.5.
|
|
73
|
+
FROM ghcr.io/papeete-hub/foundry-implementation-actor:0.5.1
|
|
74
74
|
RUN pip install --no-cache-dir kpack==2.0.1 kontract==0.1.0 # what this sidecar's ground_in names
|
|
75
75
|
COPY actor-agentic-context.yaml /actor/
|
|
76
76
|
RUN foundry-implementation-actor render-cards /actor && foundry-implementation-actor lint /actor
|
|
@@ -193,7 +193,9 @@ task does not determine this"* is a legitimate answer, and it is the most useful
|
|
|
193
193
|
|
|
194
194
|
`assess-task` is a **query**, not an action — *"a promise to answer, from this actor's own state
|
|
195
195
|
and nothing invented."* It clones read-only, grounds itself exactly as the implement door does, and
|
|
196
|
-
is invoked with
|
|
196
|
+
is invoked with `--tools Read,Glob,Grep`, so `Write`, `Edit` and `Bash` are absent from the session
|
|
197
|
+
rather than merely unapproved (0.5.0 passed only `--allowedTools`, which removes nothing — see
|
|
198
|
+
ADR-FIA-0004's amendment). A door that **cannot** write
|
|
197
199
|
beats a door asked not to. It registers no handler either: with an engine and no handler,
|
|
198
200
|
`Actor.receive()` returns the judged dict as the reply, and there is nothing to contain because
|
|
199
201
|
nothing is produced.
|
|
@@ -285,6 +287,31 @@ Publishing additionally needs `IMAGE_REGISTRY` and `BUILDKIT_HOST`. There is no
|
|
|
285
287
|
no docker socket anywhere in this design — `buildctl` is a client, which is why an actor running
|
|
286
288
|
this can be an ordinary Pod.
|
|
287
289
|
|
|
290
|
+
## What a session may spend
|
|
291
|
+
|
|
292
|
+
Environment, read once at boot by `serve`; constructor keywords on `Settings` for an embedder. The
|
|
293
|
+
budget is **not** a sidecar field: a capability declares what it is, not how long its actor may
|
|
294
|
+
think (ADR-FIA-0007).
|
|
295
|
+
|
|
296
|
+
| variable | default | what it costs to raise |
|
|
297
|
+
|---|---|---|
|
|
298
|
+
| `MAX_TURNS` | `60` | `implement-task`'s turns. A turn is a model call plus a tool call; raising it buys a slower-to-navigate repo more room, and buys a session that has lost the plot more room to keep losing it |
|
|
299
|
+
| `SESSION_TIMEOUT_S` | `1800` | `implement-task`'s wall clock. The caller's own door timeout has to exceed it, or a slow success arrives as "did not answer" |
|
|
300
|
+
| `ASSESS_MAX_TURNS` | `15` | `assess-task`'s turns. It reads and answers; it cannot write |
|
|
301
|
+
| `ASSESS_TIMEOUT_S` | `600` | `assess-task`'s wall clock. Round 0 blocks on it, before anything is built |
|
|
302
|
+
| `CLONE_TIMEOUT_S` | `120` | the full clone, per door call |
|
|
303
|
+
| `FETCH_TIMEOUT_S` | `120` | each `ground_in` fetch, per door call |
|
|
304
|
+
|
|
305
|
+
**The defaults have not moved** since these became reachable; they are what every use was already
|
|
306
|
+
running. A value that is not a positive integer is refused at boot, naming itself, rather than
|
|
307
|
+
silently falling back — so a raised budget that was misspelt crash-loops with the reason on stdout
|
|
308
|
+
instead of changing nothing. The four session knobs are on the `actor-started` record too, so a run
|
|
309
|
+
that ran out of budget can be read against the budget it actually had.
|
|
310
|
+
|
|
311
|
+
A door that runs out says so in those terms: `implement-task ran out of turns (max_turns=60) and
|
|
312
|
+
was stopped mid-work, so nothing it produced is kept — raise MAX_TURNS on this actor's Deployment,
|
|
313
|
+
or narrow the task.`
|
|
314
|
+
|
|
288
315
|
## CLI
|
|
289
316
|
|
|
290
317
|
```bash
|
|
@@ -329,6 +356,32 @@ move, not a rewrite."*
|
|
|
329
356
|
|
|
330
357
|
`adr/` records the decisions. Design rationale belongs there, not in commit messages.
|
|
331
358
|
|
|
359
|
+
## Releasing, and which registry to pin
|
|
360
|
+
|
|
361
|
+
A tag (`v*`) publishes two artifacts at one version — the wheel to PyPI, and the image to **two
|
|
362
|
+
registries** holding the same digest (ADR-FIA-0006):
|
|
363
|
+
|
|
364
|
+
| Registry | Pin it when |
|
|
365
|
+
|---|---|
|
|
366
|
+
| `ghcr.io/papeete-hub/foundry-implementation-actor` | you are writing a use's Dockerfile yourself, and build it with your own Docker |
|
|
367
|
+
| a product's own registry, `vars.PRODUCT_IMAGE` | the use is built **in-cluster**, by a builder whose one registry credential is that product's |
|
|
368
|
+
|
|
369
|
+
The second exists because `buildctl` resolves a `FROM` line client-side against the single registry
|
|
370
|
+
credential it was handed — so a use built by the cluster's shared builder can only reach the
|
|
371
|
+
product's own registry, whatever the README says. The push is skipped, with a notice, when
|
|
372
|
+
`vars.PRODUCT_IMAGE` is unset.
|
|
373
|
+
|
|
374
|
+
To give an already-released version an image in a registry it missed, run the workflow by hand from
|
|
375
|
+
the default branch with the tag as its input — it checks that tag out, and refuses it if it
|
|
376
|
+
disagrees with the `pyproject.toml` beside it:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
gh workflow run release.yml --ref main -f tag=v0.5.0
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
That run skips PyPI (which refuses a version it already holds) and does not move `latest` in either
|
|
383
|
+
registry.
|
|
384
|
+
|
|
332
385
|
## Development
|
|
333
386
|
|
|
334
387
|
```bash
|
|
@@ -47,7 +47,7 @@ ground_in:
|
|
|
47
47
|
and, beside it, a four-line Dockerfile:
|
|
48
48
|
|
|
49
49
|
```dockerfile
|
|
50
|
-
FROM ghcr.io/papeete-hub/foundry-implementation-actor:0.5.
|
|
50
|
+
FROM ghcr.io/papeete-hub/foundry-implementation-actor:0.5.1
|
|
51
51
|
RUN pip install --no-cache-dir kpack==2.0.1 kontract==0.1.0 # what this sidecar's ground_in names
|
|
52
52
|
COPY actor-agentic-context.yaml /actor/
|
|
53
53
|
RUN foundry-implementation-actor render-cards /actor && foundry-implementation-actor lint /actor
|
|
@@ -170,7 +170,9 @@ task does not determine this"* is a legitimate answer, and it is the most useful
|
|
|
170
170
|
|
|
171
171
|
`assess-task` is a **query**, not an action — *"a promise to answer, from this actor's own state
|
|
172
172
|
and nothing invented."* It clones read-only, grounds itself exactly as the implement door does, and
|
|
173
|
-
is invoked with
|
|
173
|
+
is invoked with `--tools Read,Glob,Grep`, so `Write`, `Edit` and `Bash` are absent from the session
|
|
174
|
+
rather than merely unapproved (0.5.0 passed only `--allowedTools`, which removes nothing — see
|
|
175
|
+
ADR-FIA-0004's amendment). A door that **cannot** write
|
|
174
176
|
beats a door asked not to. It registers no handler either: with an engine and no handler,
|
|
175
177
|
`Actor.receive()` returns the judged dict as the reply, and there is nothing to contain because
|
|
176
178
|
nothing is produced.
|
|
@@ -262,6 +264,31 @@ Publishing additionally needs `IMAGE_REGISTRY` and `BUILDKIT_HOST`. There is no
|
|
|
262
264
|
no docker socket anywhere in this design — `buildctl` is a client, which is why an actor running
|
|
263
265
|
this can be an ordinary Pod.
|
|
264
266
|
|
|
267
|
+
## What a session may spend
|
|
268
|
+
|
|
269
|
+
Environment, read once at boot by `serve`; constructor keywords on `Settings` for an embedder. The
|
|
270
|
+
budget is **not** a sidecar field: a capability declares what it is, not how long its actor may
|
|
271
|
+
think (ADR-FIA-0007).
|
|
272
|
+
|
|
273
|
+
| variable | default | what it costs to raise |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| `MAX_TURNS` | `60` | `implement-task`'s turns. A turn is a model call plus a tool call; raising it buys a slower-to-navigate repo more room, and buys a session that has lost the plot more room to keep losing it |
|
|
276
|
+
| `SESSION_TIMEOUT_S` | `1800` | `implement-task`'s wall clock. The caller's own door timeout has to exceed it, or a slow success arrives as "did not answer" |
|
|
277
|
+
| `ASSESS_MAX_TURNS` | `15` | `assess-task`'s turns. It reads and answers; it cannot write |
|
|
278
|
+
| `ASSESS_TIMEOUT_S` | `600` | `assess-task`'s wall clock. Round 0 blocks on it, before anything is built |
|
|
279
|
+
| `CLONE_TIMEOUT_S` | `120` | the full clone, per door call |
|
|
280
|
+
| `FETCH_TIMEOUT_S` | `120` | each `ground_in` fetch, per door call |
|
|
281
|
+
|
|
282
|
+
**The defaults have not moved** since these became reachable; they are what every use was already
|
|
283
|
+
running. A value that is not a positive integer is refused at boot, naming itself, rather than
|
|
284
|
+
silently falling back — so a raised budget that was misspelt crash-loops with the reason on stdout
|
|
285
|
+
instead of changing nothing. The four session knobs are on the `actor-started` record too, so a run
|
|
286
|
+
that ran out of budget can be read against the budget it actually had.
|
|
287
|
+
|
|
288
|
+
A door that runs out says so in those terms: `implement-task ran out of turns (max_turns=60) and
|
|
289
|
+
was stopped mid-work, so nothing it produced is kept — raise MAX_TURNS on this actor's Deployment,
|
|
290
|
+
or narrow the task.`
|
|
291
|
+
|
|
265
292
|
## CLI
|
|
266
293
|
|
|
267
294
|
```bash
|
|
@@ -306,6 +333,32 @@ move, not a rewrite."*
|
|
|
306
333
|
|
|
307
334
|
`adr/` records the decisions. Design rationale belongs there, not in commit messages.
|
|
308
335
|
|
|
336
|
+
## Releasing, and which registry to pin
|
|
337
|
+
|
|
338
|
+
A tag (`v*`) publishes two artifacts at one version — the wheel to PyPI, and the image to **two
|
|
339
|
+
registries** holding the same digest (ADR-FIA-0006):
|
|
340
|
+
|
|
341
|
+
| Registry | Pin it when |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `ghcr.io/papeete-hub/foundry-implementation-actor` | you are writing a use's Dockerfile yourself, and build it with your own Docker |
|
|
344
|
+
| a product's own registry, `vars.PRODUCT_IMAGE` | the use is built **in-cluster**, by a builder whose one registry credential is that product's |
|
|
345
|
+
|
|
346
|
+
The second exists because `buildctl` resolves a `FROM` line client-side against the single registry
|
|
347
|
+
credential it was handed — so a use built by the cluster's shared builder can only reach the
|
|
348
|
+
product's own registry, whatever the README says. The push is skipped, with a notice, when
|
|
349
|
+
`vars.PRODUCT_IMAGE` is unset.
|
|
350
|
+
|
|
351
|
+
To give an already-released version an image in a registry it missed, run the workflow by hand from
|
|
352
|
+
the default branch with the tag as its input — it checks that tag out, and refuses it if it
|
|
353
|
+
disagrees with the `pyproject.toml` beside it:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
gh workflow run release.yml --ref main -f tag=v0.5.0
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
That run skips PyPI (which refuses a version it already holds) and does not move `latest` in either
|
|
360
|
+
registry.
|
|
361
|
+
|
|
309
362
|
## Development
|
|
310
363
|
|
|
311
364
|
```bash
|
|
@@ -69,6 +69,13 @@ with `Read,Glob,Grep` and no `Write`, `Edit` or `Bash`. **A door that cannot wri
|
|
|
69
69
|
asked not to** — the same discipline as `handler.py`'s containment check standing behind the
|
|
70
70
|
implement door's write boundary.
|
|
71
71
|
|
|
72
|
+
*Amended in 0.5.1, after the first live run.* Up to 0.5.0 that list was passed only as
|
|
73
|
+
`--allowedTools`, which PRE-APPROVES the tools it names and removes nothing: a live assess session
|
|
74
|
+
ran Bash three times. The door was asked not to write after all. It is now also passed as
|
|
75
|
+
`--tools`, which is what takes every other built-in out of the session, and a test pins both flags.
|
|
76
|
+
The same run showed commitments coming back as prose strings ("E1: …", "E4/E5: …") that nothing
|
|
77
|
+
could attach to an expectation, so the prompt now requires one `{id, commitment}` object per entry.
|
|
78
|
+
|
|
72
79
|
**4. `acceptance_surface` is an OPTIONAL payload field on `implement-task`.** The door works
|
|
73
80
|
exactly as before without it. The DoD is what the caller asked for; the surface is what dev and
|
|
74
81
|
test agreed it means, and where the surface is more specific, it wins.
|
|
@@ -124,9 +131,11 @@ tuning, and it is a constructor keyword.
|
|
|
124
131
|
- **Expectation ids are worth more than this round.** Because each carries a stable `id`, a failing
|
|
125
132
|
verdict can name agreed expectations instead of scraped pytest lines, and `remediation_context`
|
|
126
133
|
can stop being free prose. Neither is done here.
|
|
127
|
-
- **The other two halves are specified
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
- **The other two halves are specified here and built elsewhere.** The testing actor's
|
|
135
|
+
`propose-acceptance` is `foundry-testing-actor` (ADR-FTA-0002), and the orchestrating actor's
|
|
136
|
+
round 0 is `foundry-task-orchestration-actor` (ADR-FTOA-0002), both extracted from their
|
|
137
|
+
instance repos the way ADR-FIA-0005 extracted this one. The round runs after that actor unpacks
|
|
138
|
+
its payload and before its attempt loop begins.
|
|
130
139
|
- **Not decided here:** whether the agreed surface should also be committed somewhere durable
|
|
131
140
|
rather than only passed between doors, and whether `definition_of_done` should eventually be
|
|
132
141
|
absorbed into it rather than sitting beside it.
|
foundry_implementation_actor-0.6.0/adr/ADR-FIA-0006-the-image-is-published-to-two-registries.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ADR-FIA-0006
|
|
3
|
+
title: "The image is published to two registries, and names neither in its source"
|
|
4
|
+
status: Proposed
|
|
5
|
+
date: 2026-09-11
|
|
6
|
+
supersedes: []
|
|
7
|
+
references:
|
|
8
|
+
- ../.github/workflows/release.yml
|
|
9
|
+
- ADR-FIA-0005-the-actor-ships-an-image.md
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ADR-FIA-0006 — The image is published to two registries, and names neither in its source
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
ADR-FIA-0005 made the base image half of what a release is: a use is one sidecar and a `FROM`
|
|
17
|
+
line, so the image is not a convenience beside the wheel, it is the thing a use is built out of.
|
|
18
|
+
The first release to carry one (0.5.0) published it to GHCR, because that is the registry a
|
|
19
|
+
GitHub-hosted package gets for free and the only one the workflow had a credential for.
|
|
20
|
+
|
|
21
|
+
That is the wrong registry for the consumer that actually exists. A use is built **inside a
|
|
22
|
+
cluster**, by the shared rootless builder `papeete-platform`'s `modules/buildkit` installs, which
|
|
23
|
+
resolves a `FROM` line client-side against the registry it holds a push token for — an Azure
|
|
24
|
+
Container Registry, provisioned by that repo's `modules/acr` with scope maps over the product's own
|
|
25
|
+
repository prefixes. `buildctl` has exactly one registry credential, mounted as its
|
|
26
|
+
`$DOCKER_CONFIG/config.json`, and it is not a GHCR one. So a `FROM ghcr.io/...` line inside that
|
|
27
|
+
builder resolves against a registry it cannot authenticate to, and the failure is a pull error at
|
|
28
|
+
build time rather than anything a gate here would catch.
|
|
29
|
+
|
|
30
|
+
Meanwhile GHCR is the right registry for the other consumer: someone reading this README and
|
|
31
|
+
writing a Dockerfile on their laptop, who has a GitHub account and no relationship with any
|
|
32
|
+
product's Azure subscription.
|
|
33
|
+
|
|
34
|
+
Two consumers, reached by two different credentials. The image has to be in both.
|
|
35
|
+
|
|
36
|
+
## Decision
|
|
37
|
+
|
|
38
|
+
The release workflow's `image` job pushes **one build to two registries**:
|
|
39
|
+
|
|
40
|
+
- **GHCR**, unconditionally, at `ghcr.io/<this repo>` — unchanged.
|
|
41
|
+
- **A product's own registry**, when `vars.PRODUCT_IMAGE` names one. The variable is the full
|
|
42
|
+
repository path (`<name>.azurecr.io/<product>/foundry-implementation-actor`); the login server
|
|
43
|
+
is its first segment; `secrets.ACR_PUSH_USERNAME` / `ACR_PUSH_PASSWORD` are the scope-mapped push
|
|
44
|
+
token `modules/acr` already emits as outputs.
|
|
45
|
+
|
|
46
|
+
`docker tag` re-points the build GHCR already holds, so both registries carry **one digest**. The
|
|
47
|
+
job does not build twice.
|
|
48
|
+
|
|
49
|
+
`vars.PRODUCT_IMAGE` unset is a valid state: a fork, or this repo before its credentials existed,
|
|
50
|
+
publishes to GHCR alone and emits a `::notice` saying so. Set-but-broken is a hard failure.
|
|
51
|
+
|
|
52
|
+
The workflow also gains `workflow_dispatch`, so a version whose wheel is already on PyPI can be
|
|
53
|
+
given an image in a registry it missed without inventing a version number. It **takes the tag as an
|
|
54
|
+
input** and is run from the default branch: GitHub takes the workflow file from the ref it is
|
|
55
|
+
dispatched on, and a tag old enough to need a backfill is by definition older than the workflow
|
|
56
|
+
that can perform one — so the checkout takes its content from the named tag while the file comes
|
|
57
|
+
from the branch. On a manual run the `publish` job is skipped — PyPI refuses a version it already
|
|
58
|
+
holds — and `latest` is **not** moved in either registry, so backfilling an old tag cannot drag it
|
|
59
|
+
backwards.
|
|
60
|
+
|
|
61
|
+
## Rationale
|
|
62
|
+
|
|
63
|
+
**Why a variable and not a literal.** This package is generic across capabilities and carries no
|
|
64
|
+
product's name; `tests/test_portability.py` enforces exactly that over `src/`, and a product name
|
|
65
|
+
hardcoded in `release.yml` would be the same mistake one directory over. A product that hosts a
|
|
66
|
+
registry names itself in its own repository variable. It also makes "no product registry
|
|
67
|
+
configured" expressible rather than a broken default.
|
|
68
|
+
|
|
69
|
+
**Why one build rather than two.** Two `docker build` invocations in one job can produce two
|
|
70
|
+
different images — a base-image tag refreshed between them is enough — and nothing downstream
|
|
71
|
+
would ever notice that `:0.5.0` meant different bytes depending on where it was pulled from. That
|
|
72
|
+
is the same failure the existing gate *the image and the wheel are one release* exists to prevent,
|
|
73
|
+
one level up.
|
|
74
|
+
|
|
75
|
+
**Why not mirror instead.** ACR can import from another registry (`az acr import`), which would
|
|
76
|
+
keep one publishing path. It needs a credential for the source, which means making the GHCR package
|
|
77
|
+
public or minting a PAT for it, and it puts a second system between the tag and the image. Pushing
|
|
78
|
+
the build we already have in hand is fewer moving parts.
|
|
79
|
+
|
|
80
|
+
**Why this repo decides this at all.** Where an ecosystem's artifacts live is an `ADR-ECO-*`
|
|
81
|
+
question, and there is no ecosystem ADR about registries today. What this ADR owns is narrower and
|
|
82
|
+
squarely this repo's: that a release publishes to more than one place, and that this package names
|
|
83
|
+
none of them in its own source.
|
|
84
|
+
|
|
85
|
+
## Consequences
|
|
86
|
+
|
|
87
|
+
- **Two secrets and one variable must exist** on this repo before the product push does anything:
|
|
88
|
+
`PRODUCT_IMAGE`, `ACR_PUSH_USERNAME`, `ACR_PUSH_PASSWORD`. The credentials come from
|
|
89
|
+
`terraform output` on `papeete-platform`'s `examples/acr-local`, and they are set by a human —
|
|
90
|
+
`papeete-foundry-product/GetSecrets.sh` is TTY-only by design so no credential passes through an
|
|
91
|
+
assistant's context, and that convention holds here.
|
|
92
|
+
- **0.5.0 shipped to GHCR only.** Its image exists at
|
|
93
|
+
`ghcr.io/papeete-hub/foundry-implementation-actor:0.5.0` and nowhere else until the manual run
|
|
94
|
+
above is used to backfill it.
|
|
95
|
+
- **The token's scope map must admit the repository.** `modules/acr`'s `repository_patterns` is the
|
|
96
|
+
caller's input, and a path outside it is refused at push time with a permissions error rather
|
|
97
|
+
than at configuration time. Widening it is a `papeete-platform` change, not one here.
|
|
98
|
+
- **A rotated push token silently stops releases.** The token password is created by Terraform with
|
|
99
|
+
no expiry by default; when that changes, the secret here has to change with it. Nothing in this
|
|
100
|
+
repo can detect the drift — the first symptom is a failed release job.
|
|
101
|
+
- **Still to realize:** a use's `FROM` line points at GHCR today
|
|
102
|
+
(`examples/…/Dockerfile`, and the real use in `papeete-foundry`). Pointing it at the product
|
|
103
|
+
registry is what makes the in-cluster builder work, and it belongs in those repos rather than
|
|
104
|
+
here — this ADR only makes the image available to be pointed at.
|