|
1 | 1 | # ADR-0006: Environment & Project — v4 (drop dev-workspace Project, unify on Package) |
2 | 2 |
|
3 | | -**Status**: Accepted (v4 — supersedes v3) |
| 3 | +**Status**: Accepted (v4 — supersedes v3) — API-surface vocabulary boundary recorded 2026-08-27 (#12473): the v5.0 `project` → `environment` rename stops at the CLI's user-facing vocabulary; three API surfaces keep `project` deliberately (see the addendum) |
4 | 4 | **Date**: 2026-05-20 (v4) |
5 | 5 | **Deciders**: ObjectStack Protocol Architects |
6 | 6 | **Supersedes**: v1 (strict tree), v2 (siblings + sys_deployment join), v3 (siblings + deferred dev-workspace `sys_project`) |
@@ -252,3 +252,132 @@ when needed. |
252 | 252 | - Power Platform: Solution → Environment model (same shape, different names) |
253 | 253 | - Salesforce: Unlocked Package → Org model (no "Project" table either) |
254 | 254 | - npm + lockfile: identity + immutable version + installation pointer |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +## Addendum (2026-08-27, #12473) — the rename stops at the CLI's user-facing vocabulary: three API surfaces keep `project` deliberately |
| 259 | + |
| 260 | +**Provenance.** Maintainer ruling on |
| 261 | +[#12473](https://github.com/objectstack-ai/objectstack/issues/12473), 2026-08-27, |
| 262 | +adjudicated in the PM decision-inbox batch, accepting that card's option 3 — |
| 263 | +verbatim, untranslated: 「其他同意」. The ruling names this addendum as the |
| 264 | +deliverable and names nothing else: no code change rides it, because on this |
| 265 | +question **not touching the code is the decision**. |
| 266 | + |
| 267 | +**Why the boundary is written here rather than left implicit.** The v5.0 breaking |
| 268 | +rename `project` → `environment` — AGENTS.md, top of file: *"No aliases. See |
| 269 | +ADR-0006. 'Project' now only means the npm/monorepo sense."* — was carried |
| 270 | +through the CLI in three passes: the command name (#10967), the entity noun in |
| 271 | +every string `os environments` prints (#12153), and the comment axis (#12432). |
| 272 | +Each pass correctly fenced the API surface out of its own scope, and none could |
| 273 | +close the question, so the same seam produced a fourth card (#12464) and then a |
| 274 | +decision card, inside one week. An unwritten boundary is re-litigated by whoever |
| 275 | +next reads the two spellings side by side — human or agent — and the cost of |
| 276 | +option 3 is exactly this section. If you arrived here from such a sighting: it is |
| 277 | +deliberate, it is below, and the way to change it is D2, not a PR. |
| 278 | + |
| 279 | +### D1 — Three surfaces retain `project` deliberately |
| 280 | + |
| 281 | +Identified by **quoted phrase, not line number**. The card that ordered this |
| 282 | +addendum anchored one of the three to a line number, and that anchor was already |
| 283 | +stale when the addendum was written; the phrases below were measured on `main` |
| 284 | +on 2026-08-27. |
| 285 | + |
| 286 | +1. **The SDK method namespace** — the `projects` block on the `@objectstack/client` |
| 287 | + client class (`packages/client/src/index.ts`), reached by callers as |
| 288 | + `client.projects.list`, `client.projects.get`, `client.projects.create`, |
| 289 | + `client.projects.update`, `client.projects.delete`, `client.projects.activate` |
| 290 | + and the remaining methods of that block, including the environment-scoped |
| 291 | + `projects.packages` methods nested inside it. Note what is *already* renamed |
| 292 | + underneath it: every method in the block calls `/api/v1/cloud/environments`. |
| 293 | + The namespace identifier is the only `project` spelling left on that face — |
| 294 | + and it is a **published** method name, so renaming it breaks every caller, |
| 295 | + while an alias is precisely what this rename does not get. |
| 296 | + |
| 297 | +2. **The control-plane response fields** — the response envelope keys `project` |
| 298 | + and `projects` that the `/api/v1/cloud/environments` endpoints return. What |
| 299 | + this repository can measure is the **consumer** side, and this addendum claims |
| 300 | + nothing beyond it: the SDK declares the unwrapped shapes (`{ projects: any[]; |
| 301 | + total: number }` on list, `{ project: any; database: any }` on create, |
| 302 | + `{ project: any }` on update), and the CLI reads `res?.projects ?? []` in |
| 303 | + `packages/cli/src/commands/environments/list.ts` and `res?.project` in the |
| 304 | + sibling `create.ts` and `show.ts`. The **producer** is the cloud control plane |
| 305 | + in `objectstack-ai/cloud`, which is not this repository: these fields cannot be |
| 306 | + renamed from here at all, which is why the rename of this surface is a |
| 307 | + cross-repo coordination, not a local edit. |
| 308 | + |
| 309 | +3. **The SDK JSDoc that travels with them** — the sentence *"Provision a new |
| 310 | + project. Delegates to `ProjectProvisioningService.provisionProject` on the |
| 311 | + server."*, on the `create` method of that same namespace. It is retained by the |
| 312 | + same ruling and for a reason of its own: it names a server-side class that is |
| 313 | + part of the contract in point 2, so renaming the sentence alone would make the |
| 314 | + comment describe the running system **less** accurately, not more. Editing it |
| 315 | + is executing option 2 below, which is permanently declined. |
| 316 | + |
| 317 | +⇒ A PR that "tidies" any of these three is not a cleanup. Under Prime Directive |
| 318 | +13 it reverses a recorded decision, and the route for that is D2 — not a |
| 319 | +changeset, and not a drive-by rename. |
| 320 | + |
| 321 | +### D2 — Option 1 (full rename) is pre-registered: **deferred, not abandoned** |
| 322 | + |
| 323 | +Option 1 is to rename **both** halves together — the SDK method namespace to |
| 324 | +`environments` and the wire fields to `environment` / `environments` — with no |
| 325 | +aliases, as a breaking major coordinated with `objectstack-ai/cloud`, carrying a |
| 326 | +migration entry. That is the option consistent with the no-alias rename, and it |
| 327 | +remains the direction of travel: the target-state vocabulary has one word for |
| 328 | +this concept, and it is `environment`. |
| 329 | + |
| 330 | +It is **pre-registered to reopen at the next planned SDK/protocol breaking |
| 331 | +major.** What defers it is not that the drift is acceptable forever, but that |
| 332 | +today it would buy a zero-pull cross-repo breaking change: no user is blocked by |
| 333 | +the `project` spelling, and the only measured cost of the drift so far is |
| 334 | +internal — the card stream this addendum exists to stop. At the next such major |
| 335 | +the coordination is already being paid for, and the rename rides with it. |
| 336 | + |
| 337 | +To whoever plans that major: this entry is your trigger. Reopen #12473's option |
| 338 | +1, coordinate the producer change with the cloud repository, and retire this |
| 339 | +addendum's D1 in the same release rather than adding an alias to soften it. The |
| 340 | +standing startup-stage retirement rule does **not** force this earlier — it |
| 341 | +governs deprecated-alias retirements, not a zero-pull cross-repo major. |
| 342 | + |
| 343 | +### D3 — Option 2 (SDK-only half-rename) is **permanently declined**, and this is why |
| 344 | + |
| 345 | +Option 2 is to rename the SDK namespace to `environments` while the wire fields |
| 346 | +stay `project` / `projects`. It is the cheapest-looking move on the board and it |
| 347 | +is refused permanently, for three reasons that do not expire with the price: |
| 348 | + |
| 349 | +- **The two halves of one contract would say different things.** The SDK would |
| 350 | + teach `environment`; the payload it hands back would say `project`. A contract |
| 351 | + that contradicts itself across a single call is worse than one that is |
| 352 | + uniformly out of date, because the reader cannot tell which half is the |
| 353 | + mistake. |
| 354 | +- **The mapping layer becomes permanent.** The CLI's translation between the two |
| 355 | + spellings exists today as an artifact of an unfinished rename, and it is |
| 356 | + cheap only while it is understood as temporary. Half-renaming promotes it to |
| 357 | + a load-bearing, permanently-maintained seam that every future consumer must |
| 358 | + reimplement. |
| 359 | +- **It is the worst shape for AI authors** — the axis this project weighs |
| 360 | + explicitly. An agent that reads the SDK learns one vocabulary, an agent that |
| 361 | + reads a response payload learns another, and every generated call site has to |
| 362 | + infer which side of the mapping it is standing on. Uniform-but-old is |
| 363 | + learnable; split-brain is a guess per call site, and a wrong guess typechecks. |
| 364 | + |
| 365 | +A prohibition without a reason gets stepped over by the next author who finds it |
| 366 | +inconvenient. The reason is above; if it is ever wrong, the reversal is a new |
| 367 | +ruling, not a rename PR. |
| 368 | + |
| 369 | +### What this addendum does not change |
| 370 | + |
| 371 | +No code, no schema, no route, no changeset. The three surfaces in D1 are correct |
| 372 | +as they stand today, and the correct action on them is **none**. The CLI's |
| 373 | +user-facing vocabulary stays fully renamed — `os environments`, its printed |
| 374 | +nouns, and its comments are all `environment`, and nothing here reopens them. |
| 375 | +Nothing here touches the `objectstack-ai/cloud` producer either. |
| 376 | + |
| 377 | +### Why an addendum, not a new ADR |
| 378 | + |
| 379 | +The question is this record's own: where the boundary between the two senses of |
| 380 | +"project" sits, and which surfaces the v5.0 rename reaches. A separate ADR would |
| 381 | +put the boundary in one file and the rename it bounds in another, so the reader |
| 382 | +who follows AGENTS.md's *"See ADR-0006"* would arrive at the record that does not |
| 383 | +carry the answer — the exact failure this addendum was ordered to fix. |
0 commit comments