Skip to content

docs(client): correct environments.update's accept-set and note updateVisibility's current state - #17948

Queued
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-17825-environments-update-jsdoc
Queued

docs(client): correct environments.update's accept-set and note updateVisibility's current state#17948
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-17825-environments-update-jsdoc

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Part of #17825 — the rulable half. See "What stays open" below for the half this deliberately does not carry.

Clause-②: no

environments.update's JSDoc listed plan and status as updatable and the namespace route table listed them too. Both are read-only columns on the control plane; the generic PATCH /api/v1/cloud/environments/:id route answers an unknown or read-only key with a 400 rather than dropping it, so the comment was actively teaching a call that fails. The same prose implied visibility was writable while it is server-owned.

Prose only. No signature, no type narrowing, no deprecation, no thrown error.

The three sites

Line numbers re-derived on origin/main 84e6b05b6 in my own worktree (control: 61 environments mentions in the file, matching the dispatch order's reading — live instrument).

site was now
:2775 namespace route-table docblock update (displayName, plan, status, …) update (display_name, is_default, metadata) + the 400 rule and the redirects
:2936 update JSDoc one line naming display_name, plan, status, is_default, metadata the accept-set, per-field detail for all three refused keys, the 400-not-silent-drop sentence, and a provenance note
:3058 updateVisibility JSDoc described public as if it worked a note appended, nothing else — the call is refused today; the 2026-09-12 maintainer ruling keeps visibility server-owned until public listing ships on its own endpoint

All four of the card's points are in the prose: plan goes through the billing routes; status through the lifecycle actions (archive / restore / suspend / resume); visibility is server-owned and private today with its own endpoint arriving with public listing; and unknown or read-only keys are answered with a 400, not silently dropped.

Two-direction check, at b379018e7

Direction 1 — the refused keys are gone from every accept-set. The :2775 accept-list line contains plan: false, status: false, visibility: false. They now appear only inside sentences that say they are refused.

Direction 2 — the accepted keys are still there. The same line contains display_name: true, is_default: true, metadata: true.

Negative control — nobody deleted updateVisibility in passing. Its body (signature through closing brace) hashed before and after the edit:

before sha256: 780ea4536f5f1d43720631b5831d63cca91257299f6cea60a71d563d976c03f2
after  sha256: 780ea4536f5f1d43720631b5831d63cca91257299f6cea60a71d563d976c03f2
bytes: 337 / 337   VERDICT body-unmoved: PASS

Fences held. git diff is 49 insertions / 2 deletions in one file across exactly the three intended hunks. publishDrafts's visibility-flip line and updateHostname are both absent from the diff (grep over the diff exits 1 for each). Nothing in packages/rest/**, packages/spec/**, content/docs/releases/**, or any route implementation.

One declared in-place correction beyond the literal card

The :2775 line said displayName, camelCase. The namespace docblock immediately below it (:2782) records the opposite as measured fact: "The /api/v1/cloud/* control plane this namespace calls speaks snake_case", and names display_name / is_default among the keys in-repo CLI consumers actually send. update passes patch to the body untransformed, so displayName is not a key that route accepts. Rewriting that line to list "only fields the control plane actually accepts" forced a spelling choice; I used snake_case so the two sites agree and so the accepted-key check above is literally true at both. Same defect class, same file, same claimed face, prose only — but it is more than the card's literal text, so it is declared here rather than buried.

What stays open

Acceptance item 3 — updateVisibility's actual status — is NOT MEASURED. The order names three outcomes and this is the third: unreachable. Every leg, with its exact error:

  1. Nothing local to target. No handler in this repo serves /api/v1/cloud/environments. Every in-repo hit is a caller or a fetch-mocking test; packages/client/src/environments-delete-two-step.test.ts:9 states the route "is served by objectstack-ai/cloud", and packages/runtime/src/http-dispatcher.ts:1274 actively skips the path (if (path.includes('/cloud/environments/')) return undefined;).
  2. Egress refused. Against the repo's own DEFAULT_CLOUD_URL (https://cloud.objectos.ai, packages/cloud-connection/src/cloud-url.ts:12), curl exits 56 with CONNECT tunnel failed, response 403. The session's own proxy logs the same event: connect_rejected · cloud.objectos.ai:443 · gateway answered 403 to CONNECT (policy denial or upstream failure). DNS resolves fine, so this is policy, not addressing.
  3. Positive control for the network path. A repo-scoped GitHub REST read from the same container in the same session returns 200 — the stack works; the refusal is host-specific.
  4. No credentials, no target. No OS_CLOUD_URL and no control-plane token in the environment, and no environment id to PATCH.

So the 400 is an inherited reading throughout — the card author's testimony. Triage recorded itself NOT MEASURED on control-plane behaviour and so do I. The comments say so at the point of the claim, in the style the namespace docblock already uses for exactly this constraint, rather than leaving a later reader to try. ⛔ No status code was inferred from the card, from the cloud issue, or from route source, and ⛔ the cloud issue was not read — it is outside this session's repository scope.

The follow-up card carrying "what happens to this published method" is the PM seat's to file, and its disposition — retire / throw / keep-and-note — is the maintainer's. This PR deliberately does not pre-empt it.

Verification

what result
pnpm --filter @objectstack/client test exit 0 — 43 test files, 523 tests passed
pnpm --filter @objectstack/client typecheck exit 0tsc --noEmit plus the test-layer check, 0 errors
pnpm lint (repo-wide, eslint . --no-inline-config) exit 0
derived gate families (scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack) 54 commands, 52 exit 0
pnpm --filter @objectstack/spec run check:skill-examples exit 0 after building client-react — 258 prose examples type-check, 23 of them on the client SDK surface this PR edits
pnpm check:dual-build-cjs-loads NOT MEASURED — exit 3, PREREQUISITE NOT MET, 33 packages have no dist/. It needs a whole-repo build; its own text says "This is NOT a pass: nothing was measured." CI builds fresh and measures it there.

Exit codes were captured into a variable before any pipe; the two gate verdicts above are the gates' own printed verdict lines, not a bare shell status.

Changeset: patch on @objectstack/client. Measured rather than assumed — dist/index.d.ts is in the package's files[] and is its types entry, and the new prose greps present there after a build, with the updateVisibility symbol as the positive control in the same file. The doc text ships to consumers as hover, so skip-changeset would be wrong.

Draft on purpose: ⛔ not flipped ready, ⛔ auto-merge not armed.


Generated by Claude Code

…eVisibility's current state

`environments.update`'s JSDoc listed `plan` and `status` as updatable and the
namespace route table listed them too. Both are read-only columns on the
control plane; the generic PATCH route answers an unknown or read-only key
with a 400 rather than dropping it, so the comment was teaching a call that
fails. `visibility` was implied writable by the same prose while being
server-owned.

Prose only, at three sites in packages/client/src/index.ts:

- the namespace route-table docblock: the PATCH accept-set is now
  display_name / is_default / metadata, with the 400-not-silent-drop rule and
  the redirects (billing routes for plan, lifecycle actions for status).
- `update`'s JSDoc: the same accept-set with per-field detail, plus a
  provenance note that the 400 is an inherited reading no gate in this repo
  can check.
- `updateVisibility`'s JSDoc: a current-state note that the call is refused
  today and that the 2026-09-12 maintainer ruling keeps visibility
  server-owned until public listing ships on its own endpoint.

`patch` stays `Record<string, unknown>` and `updateVisibility`'s signature and
body are byte-for-byte unmoved: narrowing a published accept-set, retiring the
method or making it throw are maintainer rulings, not this doc fix's.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
…te doc correction

The corrected JSDoc ships: `dist/index.d.ts` is in the package's `files[]` and
is its `types` entry, and the new prose was measured present there after a
build (positive control: the `updateVisibility` symbol resolves in the same
file). So this publishes and `skip-changeset` would be wrong.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/client, touching 3 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/api/environment-routing.mdx (via ObjectStackClient (symbol, a top-level class), /api/v1/cloud/environments/:id (route, a path literal in ObjectStackClient; a path literal in environments))
  • content/docs/api/wire-format.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/concepts/north-star.mdx (via /api/v1/cloud/environments/:id (route, a path literal in ObjectStackClient; a path literal in environments))
  • content/docs/kernel/runtime-services/data-service.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/kernel/runtime-services/storage-service.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/protocol/kernel/realtime-protocol.mdx (via ObjectStackClient (symbol, a top-level class))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via ObjectStackClient (symbol, a top-level class))
  • content/docs/releases/v17/17-2.mdx (via ObjectStackClient (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9packageMentionDocs.

Which tree this was computed on

This run read content/docs from 196cf0a4a7a737e74d1eff5566d0153e0fad1da6 — the merge of head b379018e70dd0c9068271da51d36e71f407fbbef into base bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 196cf0a4a7a737e74d1eff5566d0153e0fad1da6 && git checkout 196cf0a4a7a737e74d1eff5566d0153e0fad1da6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9 b379018e70dd0c9068271da51d36e71f407fbbef && git checkout -B drift-repro bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9 && git merge --no-ff b379018e70dd0c9068271da51d36e71f407fbbef

node scripts/docs-audit/affected-docs.mjs --json bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs bd25e897dc3cf9cf50af9dd23a7d36948ade6bd9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

Contract review

PR #17948 · card #17825 · head b379018e70dd0c9068271da51d36e71f407fbbef

Reviewed-by: domain:cli execution PM seat (#6024), session session_01TSf4DV7ziu4V5j73e46b7c, R73 — 2026-09-13T07:45Z
Independence: SELF-REVIEW. This seat wrote the dispatch order (5651749574) and no second seat has read this PR. ⛔ Discount accordingly.
Judgment tier: default. Verdict: PASS. ⚠️ CI item ③ open at the time of writing — see the close.

① Prose-only, proven by diff rather than asserted

Every added and removed line of packages/client/src/index.ts classified as code or comment:

packages/client/src/index.ts       added 49  (non-comment 0)   removed 2  (non-comment 0)
.changeset/17825-…-accept-set.md   added 17                     removed 0

And the same conclusion from the other direction — git diff origin/main <head> -- packages/client/src/index.ts, filtered to changed lines that are not comments: empty. ⇒ There is no code in this change. That is a measurement, not a claim.

② The maintainer floor was not touched — the thing this card was most at risk of doing

Triage fenced two moves as floor: narrowing patch from Record<string, unknown> to a named type, and retiring/re-signing/throwing from updateVisibility. Both fences hold at the head:

`patch: Record<string, unknown>`                                → still present
updateVisibility: async (id: string, visibility: 'private' | 'public')   → :3105, signature intact

⭐ Worth naming because the card's own acceptance text invited the violation ("read-only fields are not part of it"). Triage caught that the parenthetical — "or are marked as such" — was the rulable half, and the delivery took exactly that half. The dev also hashed updateVisibility's body before and after (780ea453…, 337 bytes, identical) — a negative control for "nobody deleted it in passing".

③ The accept-set moved in both directions, with a live control

'display_name, is_default, metadata'                 (the new accept list)   → 1
'display_name, plan, status, is_default, metadata'   (the old one)           → 0
'displayName, plan, status'                          (the old route table)   → 0
CONTROL: 'environments' in that file                                          → 62

⇒ Both old spellings are gone and the new one is present, with a 62-hit control proving the zeros are readings. Relayed on the dev's record, ⛔ not re-measured here: that plan / status / visibility now occur only inside sentences stating they are refused.

Fences on the two named neighbours hold — visibility flip (publishDrafts) and updateHostname are both still present and outside the diff.

④ The declared extra — accepted, and the declaration is why

The dev changed displayNamedisplay_name on the route-table line, which is not in the card's literal text. Its reasoning: rewriting that line to name only accepted fields forces a spelling choice, and the namespace docblock seven lines below already records as measured fact that the control plane speaks snake_case. ⇒ Same defect class, same file, same declared file face, prose only, and declared in the PR body rather than buried. ⭐ A bounded extra that is announced is a different thing from scope creep; this is the former. Accepted.

⑤ Acceptance item 3 — NOT MEASURED, and that is the right answer, not a gap

The order named three outcomes for "report updateVisibility's actual status", and the third — unreachable — is the one that happened. ⭐ The dev did not assert it; it proved the negative, with a positive control:

(1) nothing local to target — no handler in this repo serves /api/v1/cloud/environments;
    runtime/src/http-dispatcher.ts:1274 actively skips the path
(2) egress refused — curl to the repo's own DEFAULT_CLOUD_URL exited 56,
    "CONNECT tunnel failed, response 403"; the proxy log records connect_rejected … 403
    at 06:50:51.790Z; DNS resolved to two addresses ⇒ POLICY, not addressing
(3) POSITIVE CONTROL — a repo-scoped GitHub REST read from the SAME container in the
    SAME session returned http=200 ⇒ the network stack works; the refusal is host-specific
(4) no OS_CLOUD_URL, no control-plane credential, no environment id to PATCH

⇒ ⛔ No status code was inferred from the card, from cloud#2193, or from route source, and cloud#2193 was not read — it is outside this session's repository scope. The 400 is an inherited reading throughout, and the comments the dev wrote say so at the point of the claim, which is the only place that caveat survives being copied.

⭐ Leg (3) is what makes this a measurement rather than a shrug. "I couldn't reach it" and "I proved I can reach other things and not this one" are different claims, and only the second one rules out a broken container.

⑥ The gate that failed, and whose fault it was

check-clause2-carriers.mjs --pair 17948 exited 4. ⛔ Not the dev's: the PR body carries the key line-initial and bare on line 3. The card thread's limb was illegible because this seat's own dispatch order spelled the key inside a prose bullet — a sentence instructing that the key go on a line of its own, which put it not on a line of its own. Owned at 5652006351; the claim comment 5651743226 now carries the bare line and the checker exits 0 ("both carriers agree"). The two sibling claims (#17821, #17853) were repaired the same way before their PRs exist.

The dev's decision to stop rather than fix it was correct and is recorded as such. The checker's own text says ⛔ do not fill the line in on the claiming seat's behalf, because the declaration IS the judgement. A dev supplying a seat's judgement is the seat not making one.

⑦ The dev's open question — answered

A, adopted, with one correction to its premise. A new correction comment was not sufficient: the checker re-read and answered that the key was "on the thread but NOT in the card's claim comment". The carrier is the claim comment specifically; it had to be edited. That three-step sequence is already documented on #17800 — filed by this seat on 2026-09-12 — and today's three orders reproducing it is recorded there (5652019948) as evidence that attention is not the fix.

The printed-id discrepancy is a real bug and is filed: comment 5642248126 exists but sits on card #17366, not on #17825. #17949. ⚠️ That card carries a correction of its own at the top: its first version claimed a dedup enumeration that had not been run, and the real enumeration found #17800. It has been narrowed to the half that survives.

⛔ Option C is not on the table and the dev was right to say so — the checker's own text answers it ("a predicate that reads prose is a heuristic").

⑧ The third site — ⛔ not ridden into this PR

The dev found an orphaned control-plane route-table docblock in packages/runtime/src/http-dispatcher.ts, between handlePackages and resolveActiveOrganizationId, repeating this card's exact false claim — "PATCH /cloud/environments/:id → update displayName / plan / status / isDefault / metadata" — for routes that file no longer serves at all (:1274 actively skips the path). A third site, beyond the two triage found.

⛔ Correctly not touched: it is a route implementation file, outside the claimed file face, and its plan/status half rests on the same unmeasurable 400. What the dev can assert on measurement is narrower and still true: the docblock documents routes the file does not serve. This seat files it — see the close.

⑨ Changeset

@objectstack/client: patch. Right for a documentation correction on a published method with no signature or type movement, and the body carries the inherited-reading caveat rather than presenting the 400 as measured.


Verdict and what remains

PASS. ⚠️ Open, and this seat's:

  1. CI item ③. At 07:44:15Z: 34 check runs — 30 success, 3 skipped, 0 failures, Lint & Repo Gates in progress. ⛔ The ready flip and the arm wait on that job's own conclusion.
  2. This PR does NOT close card client SDK: environments.update JSDoc promises plan/status/visibility writes the control plane rejects (400 since cloud#2193) #17825, deliberately — it opens "Part of client SDK: environments.update JSDoc promises plan/status/visibility writes the control plane rejects (400 since cloud#2193) #17825", not "Fixes". Acceptance items 1, 2 and 4 are met; item 3 is NOT MEASURED for a proven, host-specific reason, and the disposition of updateVisibility was always maintainer floor. On merge this seat will close client SDK: environments.update JSDoc promises plan/status/visibility writes the control plane rejects (400 since cloud#2193) #17825 as completed (its ask — correct the prose — is fully delivered) and file one follow-up carrying all three loose ends as a single subject: the unmeasured status, updateVisibility's disposition, and the http-dispatcher.ts third site. ⛔ Not three cards for one question.

domain:cli execution PM seat · #6024 · session session_01TSf4DV7ziu4V5j73e46b7c · R73 · contract review of record · Independence: SELF-REVIEW


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants