Uh oh!
There was an error while loading. Please reload this page.
ADR-0006 D2 (SDK half): rename client.projects.* to client.environments.*, unwrap keys follow the wire, JSDoc names the endpoint (#12866) - #12885
Conversation
📓 Docs Drift CheckThis PR changes 2 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3511cc69e5fdb35a6cc25e957d50ccf79344add8 && git checkout 3511cc69e5fdb35a6cc25e957d50ccf79344add8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a77e4601007de2feac82c437415c216aff333e72 c7a6a50d3719eb45150dfaa83bd5a5edeaddc6b5 && git checkout -B drift-repro a77e4601007de2feac82c437415c216aff333e72 && git merge --no-ff c7a6a50d3719eb45150dfaa83bd5a5edeaddc6b5
node scripts/docs-audit/affected-docs.mjs --json a77e4601007de2feac82c437415c216aff333e72
|
… for the ADR-0006 D2 SDK half
cae0e24 to
92db06fCompare…and client.project(id) to client.environment(id) (#12882) The fourth project-spelled surface on the SDK client class. ADR-0006's D1 census named three surfaces and missed this one; the maintainer ruled on 2026-08-28 that it folds into the same D2 coordinated major. No aliases, same as the namespace rename: neither the old class name nor the old getter survives. Deliberately untouched, each being a different surface with its own decision: setProjectId/getProjectId (getProjectId is a cross-package protocol contract spoken by runtime and metadata-protocol) and the REST API config keys enableProjectScoping/projectResolution.
Fixes#12866
Fixes#12882
Part of #12865
⛔ LANDING CONSTRAINT — read before touching this PR
This PR must stay a DRAFT. Do not flip it ready, do not merge it, do not add it to the merge queue, do not arm auto-merge. It is one half of a coordinated cross-repo rename and it does not ship alone: shipping the SDK half by itself is ADR-0006 D3, permanently declined. The producer half is
objectstack-ai/cloudPR #1692 (card cloud#1691), which draft-parks the same way. The maintainer opens the landing window and lands both halves plus the control-plane deploy together; see the epic parent for the window checklist.What this is
The SDK half of ADR-0006 D2, ruled three times over: #12473 option 3, ADR-0006 D2 itself, and the maintainer's 2026-08-28 unpark ruling recorded on #12865. The
projectsmethod namespace onObjectStackClientbecomesenvironments, every declared unwrap key follows the wire rename, and the JSDoc stops asserting a server class that does not exist. No aliases — noclient.projectsgetter, no fallback hedge in any consumer, no mapping layer (ADR-0006 D3 declined one with reasons).Branch cut from
dc75ba857(origin/main, containing PR #12848). Verified head for every number below:cae0e248c.The rename
client.projects.*becomesclient.environments.*for all 16 methods including the nestedpackagesblock. URL paths are untouched — they were already on theenvironmentsspelling. The full before/after tables live in the changeset, which is the migration entry.Declared unwrap keys, censused across the whole block rather than pattern-matched:
listprojects+totalenvironments+totalgetproject+ database/credential/membership/organizationenvironment+ the same joined blockscreateproject+databaseenvironment(see below)update,activate,updateHostname,updateVisibility,retryProvisioningprojectenvironmentdelete,rotateCredential,listRevisions,listBranches,renameBranch,deleteBranch,listDrivers, allpackages.*project-family keyThose match the producer half's post-rename table key for key.
Truth restorations beyond the pure rename — each one measured, each one declared
Three, all inside the declared file surface, all the same defect class as the card ("the declared shape must match the wire; the JSDoc must not assert what does not exist"):
createnever answered aprojectkey at all.POST /api/v1/cloud/environmentsis served by cloud'spackages/service-cloud/src/routes/environment-lifecycle.ts, which builds its body key by key and answersenvironment+warnings+durationMs+ conditionalhostnameAssignment. So the old declaration was not merely pre-rename, it was false against the running control plane — andos environments createreadres.project.idthrough it, which means the default--activatehas been silently doing nothing and the table output has been printingundefined. The rename fixes both.createno longer declares adatabasekey. That route does not send one, and the key was declared NON-optional, sores.database.drivertypechecked and threw at runtime — the exact failure a declared shape exists to prevent.getis the method that really answers adatabaseblock; it keeps it. The three keyscreatereally does send are deliberately still undeclared: adding published surface is a separate decision, filed asenvironments.create()declares one response key; the control plane sends four (warnings/durationMs/ conditionalhostnameAssignmentundeclared) #12883.@seepath, twice.docs/adr/0002-project-database-isolation.mddoes not exist; the file isdocs/adr/0002-environment-database-isolation.md. Both occurrences inpackages/client/src/index.tsare corrected. The "Active project id" prose next to the second one is left alone — that is the ScopedProjectClient noun lane, filed as ADR-0006 leftovers in@objectstack/client:client.project()/ScopedProjectClient/ two test titles still speak the pre-v5.0 noun #12882.The JSDoc
The
createdocblock said the server delegates toProjectProvisioningService.provisionProject. Measured 2026-08-28 against the cloud repo'sorigin/main: zero hits for that spelling anywhere inpackages/service-cloud/src(positive control — the routes files that produce these envelopes are right there andgit grepfinds them). The class the control plane really constructs today isEnvironmentProvisioningServiceand the method isprovisionEnvironment.Both docblocks — this SDK's and
os environments create's — now name the endpoint rather than a server class, and say why. The class lives in a repo this one never compiles against, so a class name here rots with nothing to catch it; the endpoint is the one identifier the method itself builds and an in-repo reader can verify. Naming the currently-correct class would have re-created the exact rot in one edit.The fourth surface — the environment-scoped sub-client (#12882)
Folded in by the same maintainer ruling of 2026-08-28 (「可以」), as its own commit
e1c8d20ea. ADR-0006's D1 census named three surfaces and missed this one; thedecision record on #12882 classes it as an oversight, not a deliberate retention,
so Prime Directive 13 is not engaged by renaming it.
client.project(id)client.environment(id)ScopedProjectClient(exported class)ScopedEnvironmentClientNo aliases, same as the namespace: an
importof the old class name fails at theimport line, which is the loudest channel available. Behaviour is unchanged — the
scoped client still prefixes
/api/v1/environments/:environmentId/...and exposesthe same
data/meta/batch/packagesshape; only the guard message moves,to
[ObjectStack] environment(id): environmentId is required.Two consumers that would otherwise have been left asserting something false:
client-url-conformance.test.ts'sNON_HTTPmap is keyed by METHOD NAME, so its'project'entry had to become'environment'or the sweep's "methods neitherdriven nor declared" assertion fails; and the two
client.test.tstitles that said/projects/:idwhile their assertions pinned/environments/:idare corrected —that was the stale-title half of #12882.
Two hand-written docs asserted that
client.project(id)survives as a compatibilitymethod name. Both are corrected rather than left to ship as documented falsehoods:
content/docs/api/environment-routing.mdx(a code sample that would no longercompile, plus its migration-checklist line) and
content/docs/concepts/north-star.mdx(whose "Drift To Keep Shrinking" bullet for this exact method is replaced by the drift
that genuinely remains).
Deliberately NOT renamed, each being a different surface with its own decision —
all measured, none silently skipped:
setProjectId/getProjectIdon the clientgetProjectIdis a cross-package protocol contract:packages/runtime/src/domains/meta.tsduck-types it andpackages/metadata-protocol/src/protocol.tsdeclares it. A coordinated rename, not a local oneenableProjectScoping/projectResolutionpackages/cli/src/commands/serve.ts. A different package's published surface. The docblocks naming them are worded so they stay truepackages/spec/src/migrations/**anddocs/protocol-upgrade-guide.mdpackages/specis off-limits to this card, and rewriting a historical migration record would falsify it.changeset/*.mddocs/audits/2026-07-dispatcher-client-route-coverage.mdConsumer census
Every consumer of the
projectsmethod namespace or of theproject/projectsunwrap keys, found by grepping this whole repo plus a read-only sweep of the sibling checkouts. Nothing outside the declared surface was edited.In this PR (the declared surface):
packages/client/src/index.tsproject-family key, the block's JSDoc, and two cross-references to the "projects surface" precedent elsewhere in the filepackages/cli/src/commands/environments/list.tsprojects.list,res.projects, the local bound from itpackages/cli/src/commands/environments/create.tsprojects.create,projects.activate, threeres.projectreads, the stale provisioning-class docblockpackages/cli/src/commands/environments/show.tsprojects.get,res.project, a docblock naming the methodpackages/cli/src/commands/environments/switch.tsprojects.get,projects.activate,lookup.projectand the local bound from itpackages/cli/src/commands/environments/bind.tsprojects.get,projects.update, twocurrent.project.metadatareadspackages/client/src/client-url-conformance.test.tsCONTROL_PLANE_NAMESPACE, the literal string that bounds which namespace may reach/api/v1/cloud/— a real dependency on the namespace NAME, not a commentpackages/client/src/return-type-precision.test.tsprojects.get/client.projects.*packages/cli/src/commands/environments/environments.test.tswas measured and is clean: it derives oclif command ids from file paths and asserts nothing about the SDK namespace or the envelope keys. Itsos projects bindstring is a deliberate reverse-verification fixture for a command-id rename and is unrelated.Outside the declared surface — listed, not touched:
client.project(id)/ScopedProjectClient/ theenvironmentIdoption's "Active project id" proseprojectsurface on the same class; a published-class rename needing its own coordinated window. Filed as #12882packages/client/src/client.test.tslines 1989 / 1999/projects/:idwhile their assertions pin/environments/:id. Same card, #12882content/docs/releases/v17.mdxline 868packages/client/CHANGELOG.md,packages/cli/CHANGELOG.md, rootCHANGELOG.mdclient-url-conformance.test.tsline 154'sprojects.listTemplatesmentionprojects-namespace-coverage.test.tsandcloud-route-ledger.ts../objectuiorigin/main: zero consumers of the control-plane namespace or these keysclient.environments.create's request fields (project_type,slug,region, …)create's undeclaredwarnings/durationMs/hostnameAssignmentTests
New:
packages/client/src/client.environments-namespace.test.ts— 7 runtime tests plus three exported compile-time pin functions.The load-bearing pin is the absence of a compatibility layer, which no ordinary assertion observes: every test of
client.environmentsstays green if someone later adds aprojectsgetter beside it. So the file asserts'projects' in clientisfalseon the shape, drives each envelope through a recordingfetchand reads the keys back, and carries one@ts-expect-errorper retired key spelling. Those type pins are real, not phantom:packages/client/tsconfig.test.jsonincludessrc/**, thetypecheckscript names it, andtsc --listFiles -p tsconfig.test.jsonshows all three edited/added test files inside the program. An unused@ts-expect-erroris itself an error, so a green typecheck proves every pinned property access really does not exist — the reverse verification is structural and needs no ablation.Updated:
client-url-conformance.test.ts(the namespace bound),return-type-precision.test.ts(two docblocks).Verification — all at
cae0e248cDeclared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usableflock. The shared verify lock is declared Linux-only (flockis util-linux, and a stock macOS does not ship it), so the commands below were run directly, without the lock — a declared narrowing, not a silent one. No serialization guarantee held for these runs, nor for any sibling agent in this container while they ran.pnpm --filter '@objectstack/cli...' buildVERDICT command-exit 0(70/70 turbo tasks on the later full build)pnpm --filter @objectstack/client testTest Files 27 passed (27)·Tests 371 passed (371)Test Files 1 passed (1)·Tests 7 passed (7)pnpm --filter @objectstack/client --filter @objectstack/cli typecheckDone;check:test-typecheck: OK — @objectstack/client's test layer compiles under packages/client/tsconfig.test.json; 0 file(s) / 0 error(s)pnpm lint(repo-wideeslint . --no-inline-config, no narrowing)Gate families derived mechanically —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon the real change set (10 paths, three-dot merge-base semantics), then every derived family plus every convention-triggered one was run. 26 green, 1 red (declared below), 1 NOT MEASURED:nul-bytes,changeset-gate-self-tests,empty-changeset,changeset-no-major,objectui-changeset,pm-half-states,release-rehearsal-clone --self-test,cross-package-test-inputs(both spellings),ci-filter-parity,comment-mask-adoption,plugin-teardown-shape,objectql-double-limit,page-declaration-shape,published-files,slot-lookup,test-source-alias,type-source-resolution,query-options-erasure,engine-double-contract,where-matcher,type-check-coverage,type-check-debt,i18n,i18n-coverage, speccheck:skill-examples, clientcheck:exported-any-returns,docs-audit/check-affected-docs.check-type-check-coverage --re-measure: OK — 31 ledger entr(ies) re-measured in 63.3s, 1570 raw tsc error(s) total, none above its recorded number.·check-i18n-coverage: OK (12 config(s), 602 baselined untranslated string(s), none new).scripts/pm/check-half-states.mjsexits 3 —PREREQUISITE NOT MET — the anonymous API rate limit (60 req/h) is exhausted for this egress IP. It judges the PM board, not this diff, and it printed no finding line.i18n-coverageon an unbuilt@objectstack/connector-mcp, speccheck:skill-exampleson an unbuiltpackages/client-react/dist,type-check-debton the unbuilt closure). All three were re-run green afterturbo run buildacross./packages/*and./packages/*/*— they were prerequisite states, not findings.packages/client/exported-any-returns.jsonneeded no edit: measured, it holds zeroObjectStackClient.projects.*entries, because every method in this block returns anany-CONTAINING type rather than bareany, which that gate deliberately does not list.check:exported-any-returnsis green against the rebuiltdist.@objectstack/clisuite — 5 files red, and they are red without this changepnpm --filter @objectstack/cli testreportsTest Files 5 failed | 194 passed (199)·Tests 3 failed | 2258 passed | 14 skipped. All five were then re-run on a clean worktree detached at the merge basedc75ba857, with none of this diff present and its own--filter '@objectstack/cli...' build: identical file set, identical failing-test set (Tests 3 failed | 28 passed | 14 skipped (45)). They are threeserve-*port-drift e2e arms plus two login-NDJSON e2e files, all host-platform behaviour on macOS, none of them touching the renamed surface. Recorded as an observation in #12884, and the comparison worktree was removed afterwards.Two declared deviations from the dispatch
The changeset is
minor, notmajor. The dispatch asked formajoron@objectstack/client. Measured on this tree:scripts/check-changeset-no-major.mjsis a launch-window guard that refuses any diff introducing amajor, because every publishable package is in the Changesetsfixedgroup, so onemajorpromotes all ~70 packages at once. There is no.changeset/pre.json, so the RC exemption does not apply, and--listreports356 pending changeset(s), 0 declaring a major— nothing in the current stock does this. Sibling breaking changesets in the tree ship asminorand say so in words (manifest-kind-globs-retired.md,retire-auth-scim-provider-schema.md). The changeset therefore says BREAKING in prose and shipsminor, matching the house convention. If this coordinated window is meant to BE the next major cut, the change is one word in the changeset plus theallow-majorPR label — a release-cadence act reserved to the maintainer under Prime Directive Add missing Field.phone() helper and factory methods for Action/Dashboard/Report #15, deliberately not performed here.@objectstack/cliisminorfor the same reason and on its own merits: its--format jsonpayload keys move with the wire.check-adr-0087-registrationis RED, deliberately, and the reason is recorded in the changeset itself. The honest disposition for this change isnot-required (runtime-interface-only packages/client/src/index.ts#ObjectStackClient)— no Zod schema, nopackages/specdeclaration, no authorable key, no stored representation, soobjectstack migrate metaprovably has nothing to rewrite (measured: zeroprojectsenvelope contracts inpackages/spec/src; positive control,environmentshits do exist there). Steps 1–3 of the gate's predicate pass. Step 4 refuses it becausepackages/spec/src/api/contract.zod.tsline 164 namesObjectStackClientin a JSDoc prose comment while neither declaring nor importing it, and the predicate does not strip comments. Filed as check-adr-0087-registration refuses aruntime-interface-onlydisposition on a symbol named only in a JSDoc comment inside a.zod.ts#12881.The claim was not swapped for
not-required (no-migration-prescription), which would pass mechanically: this changeset ships a full before/after migration table, so that claim would be the self-contradiction the gate's own header documents as the ADR-0087's changeset disposition has no category for published runtime TS interfaces with no metadata surface — the #8277 exemption argument is correct, unverifiable, and will be re-litigated #8299 anti-pattern, held by a detector blind spot rather than a positive finding.registeredis the other checkable option and needs an entry inpackages/spec/src/migrations/registry.ts, which this card's ruling puts off-limits. Left stated and escalated rather than routed around. The gate runs inpr-automation.yml/cut-rc.yml, neither of which is in the six required contexts.Findings filed (all unassigned, none fixed here)
#12881 · #12882 · #12883 · #12884. Request-side sibling #3739 was already open and is referenced, not duplicated.
Authored by Claude Code subagent
agent-a1d45b69a14c2302bunder sessionsession_65d2faee-1ff4-4be8-be28-72b972c539d9(durable copy of the attribution, since editing a PR body normalises the footer below).Generated by Claude Code