Commit 109d91f6 authored by xuwang's avatar xuwang
Browse files

feat(update): roll to latest release by default; drop OTICA_VERSION



Why: keeping OTICA_VERSION in sync across many consumer repos was real
toil — the version string was duplicated in every project's env.mk, so
"get everyone current" meant editing N files. The pin was also only ever
partly real: all consumers share one ~/.otica checkout, so two projects
could never be on different framework versions at the same time anyway.

New model: ~/.otica is a plain git checkout that only moves when someone
runs `make update-otica`. It then stays on that ref (a sticky checkout)
until the next update — there is no per-build re-checkout and no
per-project OTICA_VERSION variable.

  make update-otica                  # -> newest v* release tag
  make update-otica VERSION=<ref>    # -> a specific tag/branch/sha, sticks

What changed:
- otica-update.sh: no <ref> now resolves the newest v* tag (falls back to
  main only if the repo has no releases); existing branch-ff / tag-detached
  logic unchanged.
- help.mk: removed the OTICA_VERSION default; update-otica passes VERSION=;
  `make help` prints "OTICA checked out: <ref>" instead of OTICA_VERSION=.
- otica-upgrade-check.sh: one "release available" notice for any
  behind-latest state, pointing at `make update-otica` /
  `make update-otica VERSION=<ref>`; silent only when on latest.
- envs/otica-env.mk: dropped the pin block (kept OTICA_REPO / OTICA_DIR).
- Docs (README, CLAUDE.md, GUIDE, technical reference, CONTRIBUTING) and
  the agentic docs/scripts: rewrote the pin-via-variable + per-build
  re-checkout narrative to the sticky-checkout model. The README
  integration snippet is now clone-if-missing only, matching what the
  generator skills already emit.

Note: generator skills (otica-new-project, otica-migrate-*) already emit
clone-if-missing with no OTICA_VERSION, so they needed no change. Existing
consumer Makefiles still carrying the old `git checkout ${OTICA_VERSION}`
re-assert line keep their behavior until hand-edited.

Changelog: changed
Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent b05b712d
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -127,8 +127,8 @@ Ground every answer in **working examples**, not just prose. Reference corpus:
  `sub-projects/` (aca-demo-app, aws-platform, azure-platform, docker-demo-app,
  docker-toolchain, gcp-platform, gitlab-platform, gke-demo-app, gke-platform).
  Its `Makefile`, `env.mk`, `repos.txt`, `tools.txt`, its own `docs/`, and each
  sub-project show how consumers actually wire up `OTICA_VERSION`, env vars, and
  the include line.
  sub-project show how consumers actually wire up OTICA (clone bootstrap, env
  vars, and the include line).
  Read files over the web (no checkout needed) with `WebFetch`:
  - browse a dir:  `https://code.stanford.edu/iac/examples/demo-project/-/tree/main/<path>`
  - a raw file:    `https://code.stanford.edu/iac/examples/demo-project/-/raw/main/<path>`
+5 −7
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ OTICA (Operation Toolkit for Infrastructure and Cloud Automation) is a shared Ma
make help              # List all targets with descriptions
make help grep=docker  # Filter help output
make tools-check       # Verify required tools from tools.txt
make update-otica      # Sync ~/.otica to OTICA_VERSION (pinned tag or branch)
make update-otica      # Sync ~/.otica to latest release (or VERSION=<tag|branch>)
```

Typical Terraform workflow in a consumer project:
@@ -63,17 +63,15 @@ Terraform source lives in `TF_DIR` (default `Terraform/`) as `.tf.tmpl` files us

### Consumer Project Bootstrap

Projects auto-install OTICA and assert their pinned version with this pattern in their `Makefile`:
Projects auto-install OTICA with a clone-if-missing pattern in their `Makefile`:
```makefile
OTICA_VERSION ?= main
ifeq ($(MAKELEVEL),0)
    ifeq ($(wildcard ${OTICA_DIR}/.git),)
        _ := $(shell git clone -q ${OTICA_REPO} ${OTICA_DIR})
    endif
    _ := $(shell git -C ${OTICA_DIR} checkout -q ${OTICA_VERSION} 2>/dev/null || true)
endif
```
The `MAKELEVEL` guard prevents re-running on recursive make calls. The checkout re-asserts the pin (no network) on every top-level run, so the shared `~/.otica` stays on *this* project's `OTICA_VERSION` even when other projects pin a different version.
The `MAKELEVEL` guard prevents re-running on recursive make calls. There is **no** per-build re-checkout: `~/.otica` is a plain git checkout that stays on whatever ref it was last set to. Its ref (the framework version, shared by every consumer on the machine) only changes when someone runs `make update-otica` — to the latest release, or to `make update-otica VERSION=<tag|branch>` for a specific ref, which then sticks. There is no per-project `OTICA_VERSION` variable.

### Releases & Versioning

@@ -81,7 +79,7 @@ The `MAKELEVEL` guard prevents re-running on recursive make calls. The checkout

**Mirroring:** code.stanford.edu is canonical; gitlab.med is an automatic **push mirror** (branches + tags), so a release pushed to `origin` reaches med on the next mirror sync (GitLab rate-limits push mirrors to about once every 5 minutes). Never release to or push directly at med — it's overwritten to match code. GitLab Release *objects* aren't mirrored (only tags), which is all consumers need to pin.

`CHANGELOG.md` is generated from `Changelog:` git trailers (config in `.gitlab/changelog_config.yml`). Consumers pin a release via `OTICA_VERSION` and are nudged to upgrade by `otica-upgrade-check.sh` (release-tag-aware). See the [README](README.md) "Releases & Versioning" and [CONTRIBUTING.md](CONTRIBUTING.md).
`CHANGELOG.md` is generated from `Changelog:` git trailers (config in `.gitlab/changelog_config.yml`). Consumers move to a release with `make update-otica` and are nudged to upgrade by `otica-upgrade-check.sh` (release-tag-aware). See the [README](README.md) "Releases & Versioning" and [CONTRIBUTING.md](CONTRIBUTING.md).

### Agentic Workflow (developer)

@@ -91,7 +89,7 @@ A four-agent loop that drives the GitLab issue → fix → review → merge life
- **Plumbing:** `scripts/gl-flow.sh` is the GitLab REST dispatcher (sources `gitlab.sh`); `agent-start.sh` (`make agents`) launches the loops as `claude --bg` background sessions; `agent-tick.sh` runs one headless tick (cron / `make agentic-tick`); `agent-stop.sh` (`make agents-stop`) stops them; `agent-bots.sh` (`make agentic-bots`) provisions per-role bot identities.
- **Identity:** authenticates with `OTICA_SERVER_TOKEN[_FILE]` (default `~/.otica-token`), the same knob as releases. GitLab forbids approving/merging your own MR, so reviewer/integrator need a **different** identity than the resolver — `make agentic-bots ARGS="provision-all"` creates per-role project-access-token bots in `~/.otica-tokens/<role>.token` (auto-selected by `agent-start.sh`/`agent-tick.sh`). Without bots everything runs as one identity (handles others' MRs; your own wait for a human).
- **Settings:** per-role least-privilege presets in `agent-instructions/agentic-*.settings.json` (passed via `--settings` for headless/background runs). `.claude/commands` and `.claude/agents` are tracked; `.claude/settings*.json` stay gitignored.
- **Run from a dedicated clone, NOT `~/.otica`.** `~/.otica` is the shared framework install that consumer projects check out to their pinned `OTICA_VERSION` on every build; the resolver's `git switch` there would corrupt that pinning. Clone OTICA elsewhere (e.g. `~/otica-agents`) and run `make agents` there — it refuses (asks to confirm) if launched from `~/.otica`, and `agent-tick.sh` logs a warning.
- **Run from a dedicated clone, NOT `~/.otica`.** `~/.otica` is the shared framework install every consumer project uses; the resolver's `git switch` there would move it out from under them. Clone OTICA elsewhere (e.g. `~/otica-agents`) and run `make agents` there — it refuses (asks to confirm) if launched from `~/.otica`, and `agent-tick.sh` logs a warning.

Full runbook: `agent-instructions/agentic-workflow.md`.

+2 −2
Original line number Diff line number Diff line
@@ -143,8 +143,8 @@ merges once the Definition of Done is met and the change is approved.
## Releases (maintainers)

Releases are cut from `main` with `make release-*` (semantic version, generated
changelog, tag, and a GitLab Release). Consumers pin a release via
`OTICA_VERSION`. See **Releases & Versioning** in the [README](README.md#releases--versioning)
changelog, tag, and a GitLab Release). Consumers move to a release with
`make update-otica`. See **Releases & Versioning** in the [README](README.md#releases--versioning)
for the full process and the required `OTICA_SERVER_TOKEN[_FILE]`.

## Get help
+26 −31
Original line number Diff line number Diff line
@@ -44,12 +44,15 @@ git clone https://code.stanford.edu/iac/cloud-framework.git ~/.otica
### Update OTICA

```bash
make update-otica
make update-otica                  # move ~/.otica to the latest release
make update-otica VERSION=v1.2.0   # or to a specific tag, branch, or SHA
```

This syncs `~/.otica` to the version your project tracks (`OTICA_VERSION`): a
pinned release tag is checked out exactly; `main` is fast-forwarded to the latest
HEAD. See [Releases & Versioning](#releases--versioning) below.
`~/.otica` is a plain git checkout. With no argument `make update-otica` moves it
to the **newest release tag**; pass `VERSION=<tag|branch|sha>` to move it to a
specific ref. Either way the checkout then **stays put** until the next
`make update-otica` — nothing re-checks it out on a normal `make` run. See
[Releases & Versioning](#releases--versioning) below.

## Releases & Versioning

@@ -57,29 +60,24 @@ OTICA is published as **semantic-version releases** (git tags `vMAJOR.MINOR.PATC
e.g. `v1.2.0`) with a generated [`CHANGELOG.md`](CHANGELOG.md) and a GitLab
Release. The current version lives in [`VERSION`](VERSION).

Because every consumer shares one `~/.otica` checkout and `make` re-asserts the
pin on each top-level run, **pinning is how a project insulates itself from
framework changes** — a new framework release reaches a project only when that
project bumps `OTICA_VERSION`.
All consumers on a machine share one `~/.otica` checkout, so its checked-out ref
*is* the framework version they all see. That ref only changes when you run
`make update-otica`.

### Pinning OTICA_VERSION (consumers)
### Tracking a version (consumers)

In your project's `env.mk`:
There is no per-project version variable to maintain. The framework version is
simply whatever `~/.otica` is checked out to:

```makefile
OTICA_VERSION=v1.2.0   # pin to a release (recommended for prod)
# OTICA_VERSION=main   # track latest framework HEAD (no pinning)
```

- The **FRAMEWORK SYNC** block in your `Makefile` checks out `OTICA_VERSION` on
  every top-level `make` (no network), so the shared `~/.otica` is always on
  *your* project's version even if another project pinned a different one.
- `make update-otica` fetches tags and moves the checkout to `OTICA_VERSION`.
- `make help` prints a non-blocking notice when a newer release is available, or
  when you are unpinned on `main`.
- `make update-otica` → newest release tag (recommended default).
- `make update-otica VERSION=v1.2.0` → a specific tag, branch, or SHA. The
  checkout then **stays on that ref** until the next `make update-otica` — this
  is how you "pin": pick a ref once and don't update again.
- `make help` shows the current checkout (`OTICA checked out: …`) and prints a
  non-blocking notice when a newer release is available.

To upgrade: read the [`CHANGELOG.md`](CHANGELOG.md) (especially **Breaking
Changes**), bump `OTICA_VERSION`, then run `make update-otica`.
Changes**), then run `make update-otica`.

### Commit trailers (contributors)

@@ -130,7 +128,7 @@ prompting for confirmation before any mutation.
of it (branches **and** tags), so a release pushed to `origin` propagates to med
with no second publish step. GitLab refreshes push mirrors **at most once every
~5 minutes**, so allow a few minutes for a new tag to appear on med. Consumers
may clone from **either** host and pin the same `vX.Y.Z` tags.
may clone from **either** host and check out the same `vX.Y.Z` tags.

```
                make release  →  origin (code.stanford.edu)   ← canonical
@@ -242,7 +240,7 @@ Notes:
│   ├── otica-changelog.sh      # Render changelog notes via the GitLab changelog API
│   ├── otica-release.sh        # Cut a release: validate, tag, push, publish
│   ├── otica-server-token.sh   # Resolve the OTICA hosting-server API token (sourced)
│   ├── otica-update.sh         # Sync a consumer checkout to OTICA_VERSION (tag/branch)
│   ├── otica-update.sh         # Sync a consumer checkout to latest release (or a tag/branch)
│   └── otica-upgrade-check.sh  # Release-aware "newer version available" notice
├── envs/                        # Environment configuration templates
│   ├── otica-env.mk
@@ -288,7 +286,6 @@ deploy → release → prod deploy) is documented in
# Envvars shared with all environments
OTICA_REPO=https://code.stanford.edu/iac/cloud-framework.git
OTICA_DIR=${HOME}/.otica
OTICA_VERSION=v1.0.0   # pin to a release; or `main` to track HEAD
```

2. **Add OTICA sync to your `Makefile`**:
@@ -298,16 +295,14 @@ include env.mk
export

# FRAMEWORK SYNC
# Clone if missing, then assert the pinned OTICA_VERSION on every top-level run
# (no network — the tag is local from the clone or `make update-otica`).
OTICA_VERSION ?= main
# Clone OTICA if it's missing. The checkout's ref (the framework version) is
# managed out-of-band with `make update-otica`; nothing re-checks it out here.
ifeq ($(MAKELEVEL),0)
    _ := $(shell >&2 echo)
    ifeq ($(wildcard ${OTICA_DIR}/.git),)
        _ := $(shell >&2 echo Clone OTICA framework from ${OTICA_REPO})
        _ := $(shell git clone -q ${OTICA_REPO} ${OTICA_DIR})
    endif
    _ := $(shell git -C ${OTICA_DIR} checkout -q ${OTICA_VERSION} 2>/dev/null || true)
endif
# END FRAMEWORK SYNC

@@ -450,8 +445,8 @@ fixes them (or answers how-to/question issues and closes them), **reviewer**
reviews the MR, and **integrator** merges approved, green MRs to `main`.

> **Run these from a dedicated OTICA clone, not `~/.otica`.** `~/.otica` is the
> shared install consumer projects check out to their pinned `OTICA_VERSION`; the
> resolver's branch switches would corrupt that. `git clone` OTICA elsewhere
> shared install every consumer project uses; the resolver's branch switches
> there would move it out from under them. `git clone` OTICA elsewhere
> (e.g. `~/otica-agents`) and run there.

```bash
+7 −8
Original line number Diff line number Diff line
@@ -7,9 +7,8 @@ they never step on each other.

## Quickstart
1. **Use a dedicated clone — NOT `~/.otica`.** `~/.otica` is the shared framework
   install that consumer projects check out to their pinned `OTICA_VERSION` on every
   build; the resolver's `git switch` would corrupt that. Clone OTICA somewhere of
   its own:
   install every consumer project uses; the resolver's `git switch` would move it
   out from under them. Clone OTICA somewhere of its own:
   ```bash
   git clone git@code.stanford.edu:iac/cloud-framework.git ~/otica-agents
   cd ~/otica-agents
@@ -247,11 +246,11 @@ claude --bg --name agent-reporter "/agent-reporter \"<bug / feature / question>\
```

> **Run from a dedicated clone, never `~/.otica`.** `~/.otica` is the shared
> framework install that consumer projects check out to their pinned `OTICA_VERSION`
> on every top-level `make` run. The resolver does `git switch` in its checkout each
> fix — doing that in `~/.otica` corrupts consumers' version pinning and races their
> `make` runs. Clone OTICA elsewhere (e.g. `~/otica-agents`) and run `make agents`
> there. `make agents` refuses (asks to confirm) if you launch it from `~/.otica`.
> framework install every consumer project uses. The resolver does `git switch` in
> its checkout each fix — doing that in `~/.otica` moves the checkout out from under
> consumers and races their `make` runs. Clone OTICA elsewhere (e.g. `~/otica-agents`)
> and run `make agents` there. `make agents` refuses (asks to confirm) if you launch
> it from `~/.otica`.
> (Reviewer/integrator only read remote refs + the API, so they're unaffected; it's
> the resolver's branch switch that's the hazard.)

Loading