feat(cli): canonical image source policy and declared forks
- Dominant language
- Python
- Stars
- 0
- Forks
- 2
- Avg merge
- 7h 6m
- Merged PRs (30d)
- 107
Description
Part of Borrow, Prove, Restore (Azure/osdu-spi#158), Phase 2. Continues #130, whose trust half shipped in #174 and #179.
## Problem
ADR-032 and ADR-033 describe a source policy and a declaration format that the code does not have. `spi onboard` trusts a repository but cannot promote its image to canonical: `--canonical-source` appears nowhere in `src/spi`, no `spi-source-` resource-group tag is written or read, and `spi up` resolves every service from the community registry regardless of trust. The declaration in `src/spi/environment.py` is the flat seven-key schema; it has no `forks:` list, `spi up` takes no `--declaration`, and the refresh and upgrade workflows reconcile nothing from it. Until this lands a fork PR can borrow a slot with an ephemeral pin (#129) but a promoted fork image never becomes the environment's standing source, so Azure/osdu-spi#155 cannot exercise the full path.
## Required change
The decision records are the spec. Read ADR-032 (Declared intent wins, Onboarding plans by default), ADR-033 (Decision), ADR-029 (Identities belong to the lifecycle), and `docs/design/fork-deployment.md` before starting, and implement what they describe rather than redesigning it.
1. **Source policy on the CLI.** `spi onboard --repo / --canonical-source fork|community`. `fork` writes the RG tag `spi-source-=/`; `community` writes `community`; omitting the option preserves the recorded value and defaults a new service to community. The source must equal the trusted repository for that service. Tag writes merge only the owned tags and preserve `spi-name-suffix`, the declaration locator, and unrelated tags. `--list` shows the source beside the trusted repository. `--remove` returns the service to community.
2. **Resolution honors the source.** `spi up`, `spi reconcile --image-branch`, and the refresh path resolve a service whose source is `/` from `ghcr.io//` at the fork's `main` digest, using the mapping in `images.py` (`fork_package_repository`), and everything else from community. A missing or private package is an error, not a fallback. The lock carries the source alongside the image so `spi info --json` and `spi service list` show where each image came from.
3. **Schema stays gated.** Onboard trusts a repository for `schema` but refuses `--canonical-source fork` until `ghcr.io//schema-load` exists at the selected commit, and the refusal names the missing package. Today `require_known_service` in `onboard.py` rejects schema outright; change that to trust-only.
4. **Declaration `forks:`.** Extend `EnvironmentDeclaration` with an optional `forks` list of `{service, repo, canonicalSource}` (`canonicalSource` defaults to `community`). `repo` unique across the list, `service` known to `IMAGE_REGISTRY`, `forks` invalid with any profile but `core`. `spi up --declaration /:` loads the reviewed file from `main`, records the locator in the RG tag `spi-environment-declaration`, and refuses explicit flags that conflict with it. A later `spi up` without the option reads the retained locator.
5. **Lifecycle reconciliation.** On a declared environment, `spi up` bootstrap and the refresh path reconcile the deploy identity's federated credentials and the source tags to `forks:` before projecting into the lock, serially per identity with the backoff ADR-032 requires. `spi onboard` on a declared environment refuses an addition, removal, repository change, or source choice that disagrees with the declaration and says which file to change.
6. **Docs.** `docs/design/fork-deployment.md` gets the `--canonical-source` mechanics and a `forks:` example; `docs/design/environment-lifecycle.md` gets the reconcile order. ADRs need no change unless the implementation deviates; if it must, amend the record in the same PR and say why. No em dashes; follow `docs/STYLE.md`.
## Acceptance
- `spi onboard partition --repo / --write --canonical-source fork` leaves `spi-source-partition=/` on the resource group and the next `spi reconcile --image-branch master` pins partition to a `ghcr.io//partition@sha256:` digest; `--canonical-source community` returns it to `community.opengroup.org` on the next reconcile with the credential still present.
- `spi onboard schema --repo / --write` succeeds; adding `--canonical-source fork` on a fork with no `schema-load` package exits nonzero naming `ghcr.io//schema-load`.
- A declaration with `forks:` and `profile: minimal` fails validation; a duplicate `repo` fails validation; `spi up --declaration` on a fresh environment records the locator and leaves credentials and source tags matching the file.
- `spi onboard` against a declared environment refuses an undeclared repository and names the declaration file.
- Unit tests cover the planner rows and steps for each source transition, declaration validation, and resolver selection between community and fork packages, in the style of `tests/test_onboard.py` (canned observed state, no simulated GitHub or Azure).
## Verify
```bash
uv run pre-commit run --all-files
```
Report the result in the PR description. The end-to-end acceptance lines above need a real environment; state in the PR which of them you ran and which a maintainer must run on the shared environment.
## Out of scope
- Entitlements for the fork identity and the no-access test identity (#132).
- The borrow, prove, restore job in the service template (Azure/osdu-spi#155).
- Moving `ops/environments/shared.yaml` to a fork declaration for the shared environment; that is a follow-up PR by a maintainer once this merges.
## Constraints
- Branch: `feat/canonical-source-policy`. Conventional Commits; PR title `feat(cli): canonical image source policy and declared forks`. Split into two PRs (source policy, then declarations) if the first passes 1500 changed lines.
- Do not merge, do not force-push, no generated-with footers or co-author trailers on commits or the PR.
- PR description follows the shape in `CONTRIBUTING.md`.
Contributor guide
Research direction
Start with ADR-032, ADR-033, ADR-029, and docs/design/fork-deployment.md, then inspect src/spi/environment.py, images.py, and onboard.py alongside tests/test_onboard.py. Run the stated pre-commit command and use the acceptance cases to verify source transitions, declaration validation, resolver selection, and lifecycle reconciliation; real-environment checks must be reported separately for maintainers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, github, python
- Domain
- cli, cloud, devops
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 28/100