Skip to content

os package publish: one command from a local checkout to the org's environment — pick the only environment, keep it across login, fail loudly without one, and publish dev builds without hand-bumping the version #30871

Closed

Description

@objectstack-fleet

coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e

Filing gate: ③ a task on the maintainer's word. In the coordinator session on 2026-11-06 the maintainer said 「我觉得先把受管软件包的开发循环走顺了」, then 「同意,然后第一步先解决我本地开发的软件包怎么能够方便地发布到云端」.

Reader: domain:cli (packages/cli/src/commands/package/publish.ts, commands/cloud/login.ts, commands/environments/*, utils/cloud-config.ts, utils/active-environment.ts). The epic and the server half are on objectstack-ai/cloud, linked in a comment below once filed. Suggested priority p1: this is the first step the maintainer named. Triage decides.

Dedupe:

维护者速读

程序员在本地改好一个受管软件包,想马上在云端自己组织的环境里看到效果。现在要过五道坎:先手动 os build;每次手改版本号,否则重复发布会 409;明明组织只有一个环境还得 switch,而 os cloud login 会把记下的环境清掉;--install 找不到环境时只打一行错误,命令照样算成功;装完说「已安装」,其实环境还没换成新版本。这张卡把 CLI 这一侧理顺,「等到真正生效」需要服务器提供信号,那一半在 cloud 卡上。

What happens today (objectstack origin/main a2f523aa)

  1. No build step. publish.ts:498-510 reads dist/objectstack.json or exits with "Run objectstack build first".
  2. Version. deriveVersion (publish.ts:221-229): --version, else manifest.version, else 0.0.0-dev.<YYYYMMDDHHMMSS>. manifest.version wins, so republishing an unchanged config always gets cloud's 409 "Version '…' already exists … Bump the version and retry" (cloud routes/package-publish.ts:1230-1236).
  3. Environment. --env / $OS_ENVIRONMENT_ID, else cloud.json.activeEnvironmentId when its url matches (publish.ts:795-818, utils/active-environment.ts:115-130).
    • No auto-select when the organization has exactly one environment (no such fallback anywhere in cli/src or client/src). ADR-0024 (cloud) now gives every organization exactly one environment.
    • os cloud login rewrites cloud.json whole (cloud-config.ts:91-97, login.ts:222-228), so activeEnvironmentId is gone after every login.
    • --install with no environment prints "--install requires --env <id> … Skipping auto-install." (publish.ts:812-818), publishes anyway, and exits 0.
  4. "Installed" is printed before the environment serves it. The versions call returns after cloud writes the desired state and evicts the kernel. The rebuild happens later (cloud package-install/install.ts:825-855). The CLI prints "Installed into environment" (publish.ts:858-863) and checks nothing more.

Ask

  1. Pick the only environment. With --install and no --env, ask the control plane for the caller's organization's environments. Exactly one → use it, and print which. None or several → refuse (item 3).
  2. Keep the active environment across os cloud login. Merge into cloud.json instead of overwriting, keeping activeEnvironmentId when the url is unchanged.
  3. --install that cannot resolve an environment exits non-zero, before the publish call, naming how to pass one. A publish that was asked to install and didn't must not read as success.
  4. Dev builds without hand-bumping. os package publish --dev publishes <manifest.version>-dev.<YYYYMMDDHHMMSS> (semver pre-release), whatever manifest.version says. Cloud already flags -dev versions as pre-release (package-publish.ts:1407); making cloud honour that flag is the cloud card's part.
  5. Build first, optionally. --build runs the same compile os build runs before reading the artifact. Name the flag as the CLI's other commands would.
  6. Wait until live (Blocked-by the cloud card's served-version signal). After --install, poll until the environment reports serving the new version, or report the build failure the environment reports. --no-wait keeps today's behaviour. Bounded by a timeout that names what it was waiting for.

Out of scope here, listed on the epic: a watch mode, a change preview before install, os mcp connect.

Acceptance

  • Tests pin: the one-environment pick, several-or-none refused, login keeping activeEnvironmentId, --install without an environment exiting non-zero before any write, and --dev producing a semver pre-release from a fixed manifest.version.
  • Item 6 has a test against a stubbed control plane: serving the new version → success; a build failure → the reason printed, non-zero.
  • On cloud staging, from a checkout with a fixed manifest.version: os package publish --dev --install run twice in a row succeeds both times without --env, and the second run's version is what the environment serves.

🤖 Generated with Claude Code

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e

    Epic: objectstack-ai/cloud#3652 (managed package dev loop, Phase 1). The server half, including the served-version signal item 6 waits on, is objectstack-ai/cloud#3651. · 2026-11-06T08:03Z

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e

    Scope amendment: developers should not have to remember a command · 2026-11-06T08:21Z

    Maintainer, coordinator session 2026-11-06, in order:

    • On os package publish --build --dev --install: 「你的命令行会不会参数太多啊?」
    • On a separate os deploy: 「开发人员也记不住啊」
    • On the proposal below: 「同意」

    This amendment supersedes the Ask's items 4–6 (--dev, --build, wait-until-live on os package publish). Items 1–3 stand unchanged and apply to every command that installs: pick the only environment, keep it across os cloud login, exit non-zero when no environment resolves.

    What replaces items 4–6

    1. One deploy command whose defaults are the dev loop. With no flags it compiles, publishes <manifest.version>-dev.<YYYYMMDDHHMMSS>, installs into the organization's only environment, and waits until the environment serves it (the wait is Blocked-by objectstack-ai/cloud#3651 item 3). A failed build prints the environment's reason and exits non-zero.
      • Working name os deploy (alternative os push). The lane picks the name. ⛔ Do not reuse the retired os publish id: publish.ts's header documents it as removed.
      • Flags only for the uncommon cases, for example --env when an organization has several environments, and --no-wait.
    2. os dev offers it on a key. Once the local dev server is up, its last line says something like 「按 d 部署到云端环境 」. Pressing d runs item 1 and prints the result in the same terminal. If not logged in, the line offers login and connect instead.
      • ⛔ No deploy on save: on cloud the organization's one environment is production (ADR-0024), so a person presses the key every time.
      • Today os dev has no key handling (packages/cli/src/commands/dev.ts).
    3. The scaffold ships npm run deploy. create-objectstack templates (for example src/templates/blank/package.json, which has dev / start / build today) gain a deploy script calling item 1.
    4. The CLI says the next step. After a successful os build, print how to deploy. Running bare os lists the few everyday commands first.
    5. The skill teaches it. The objectstack skill an AI assistant loads (skills/, platform/CLI guidance) describes the deploy flow: when to deploy, the command, how to read a failure. This part may route to domain:skills as its own card; the claiming seat decides.

    os package publish keeps its meaning: publish a release version (the manifest's version) to the organization's catalog, with --install optional. It gets items 1–3 and nothing else.

    Acceptance (replaces the Ask's acceptance for items 4–6)

    • Tests pin the deploy command's defaults: a dev version from a fixed manifest.version, the only environment picked, waiting and success on the served version, the reason and a non-zero exit on a failed build (stubbed control plane).
    • os dev test: the deploy key runs the deploy path; with no login it offers login.
    • Scaffold test: a new project's package.json has deploy.
    • On cloud staging, from a fresh scaffold with a fixed manifest.version: npm run dev, press d twice with an edit between. Both deploy without any flag, and the environment serves the second build.

    🤖 Generated with Claude Code

  4. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    Claim: PM loop round R5 · 2026-11-06T08:28Z
    Session: session_01Q4EjjRhUZjdy8VXs6Kkur2
    Account: hotlong (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-30871-deploy-dev-loop
    Worktree: /home/user/w30871/objectstack-issue-30871
    Domain: domain:cli (claimed by the epic owner of objectstack-ai/cloud#3652; no domain:cli seat is posted, and the maintainer told this seat, verbatim, 「专题卡3652相关的任务你也都优先派发」)
    Seat: repo:cloud#3
    File surface: packages/cli/src/commands/package/publish.ts, commands/cloud/login.ts, commands/environments/*, utils/cloud-config.ts, utils/active-environment.ts, a new deploy command under packages/cli/src/commands/, commands/dev.ts (the deploy key), create-objectstack templates (deploy script), the post-build and bare-os hints, and tests
    Container & model: M, mode:subagent, model: TIER_DEFAULT, effort: high
    Clause-②: no — CLI commands and flags; no spec key, schema or error code is added (re-judged on the diff)
    Metadata-protocol: none
    Responsibility: the CLI half of the managed-package dev loop, Phase 1 | none | every developer deploying a local package to the organization's environment
    Thread-read: 6680520228
    Premise: holds — the card's readings on objectstack origin/main a2f523aa, with the scope amendment 6680520228 (maintainer 「同意」) superseding items 4–6
    Serial constraints cleared: no in-flight claim on packages/cli/src/commands/package/publish.ts or dev.ts found. The wait-until-live step depends on objectstack-ai/cloud#3651 item 3 (in flight under this seat); the seat relays that contract, and this PR does not land before the contract is fixed.
    Carrier: objectstack (CLI) for items 1–3 and the amendment; the served-version signal is cloud's (objectstack-ai/cloud#3651). The amendment's skill part (item 5) routes to domain:skills as its own card once the command name is fixed.

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · domain:cli → domain:server · p1 kept · 2026-11-06T08:53Z

    ⛔ Not a claim.


    Generated by Claude Code

  7. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Nov 6, 2026
  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2
    os-dev-report

    {
     "issue": 30871,
     "status": "done",
     "branch": "claude/issue-30871-deploy-dev-loop",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30933",
     "session": "session_01Q4EjjRhUZjdy8VXs6Kkur2",
     "premise_still_valid": true,
     "summary": "Command name: `os deploy` (not `os push`: it matches the amendment's `npm run deploy` script, it is the verb developers know for put-it-live, and `push` reads as `git push`; the retired `os publish` id stays retired). Usage: `os cloud login` once, then `os deploy` with no flags = build (child `os compile`) -> publish MANIFEST_VERSION-dev.YYYYMMDDHHMMSS (UTC) -> install exactly that version into the organization's only environment -> wait until `data.served.state` is live; flags only `--env ID`, `--no-wait`, `--wait-timeout SECONDS` (default 600), `--server`, `--token`. Card items 1-3 land for every command that installs into a cloud environment (`os package publish --install` and `os deploy`, one resolver `utils/install-target.ts`: --env, then cloud.json's active environment for this plane, then GET /api/v1/cloud/environments with exactly one non-archived non-system row; none/several/unreadable exits 1 before any write); `os cloud login` keeps activeEnvironmentId for the same plane and account. `os dev` offers `d` once the first server listens (TTY only; not logged in -> `os cloud login` then deploy; no save hook; the child drops the local OS_ENVIRONMENT_ID), the create-objectstack blank template and `os init` app template ship `deploy: objectstack deploy`, `os build` prints the next step, bare `os` lists EVERYDAY COMMANDS first. The wait is wired to the seat-relayed cloud#3662 contract (GET .../environments/ID/packages/MANIFEST_ID -> data.served; never the installations probe; one wake request to the environment origin's /api/v1/discovery before each poll; superseded desiredVersionId is exit 1; no `served` key -> says it cannot confirm, exit 0). PR is a draft and must not land before cloud#3662 merges, stated at the top of the body. `os package install` targets a runtime URL and names no environment, so it had nothing to resolve.",
     "tests": "At b7ea5f21 (final head), lock VERDICT command-exit 0: cli build exit 0; `pnpm --filter @objectstack/cli typecheck` exit 0; 13 targeted files (new deploy/dev-deploy-key/next-steps/cloud-login tests, every package-publish-* file, publish-active-environment-store, init, validate-build-gate-parity, child-env-source-loader.pin) Test Files 13 passed (13), Tests 210 passed (210). At 7664160b (after merging origin/main, 4 commits with no overlap; rebuilt spec, plugin-auth, service-automation, plugin-approvals, cli): `pnpm --filter create-objectstack test` Test Files 24 passed (24), Tests 370 passed (370); create-objectstack typecheck exit 0 at 4d010654. Full cli unit tier `vitest run --project unit --maxWorkers=2` at 4d010654: Test Files 1 failed | 392 passed (393), Tests 1 failed | 5477 passed (5478); the red (validate-build-gate-parity: BUILD_NEXT_STEP imported as an unclassified value) fixed in 8df29def74, file then 33/33. NOT MEASURED at the final head: full cli unit tier, reason: shared lock queue; CI Test Core runs it. Integration tier declared to CI (no integration file, bin entry or serve-process helper touched). Ablations (12, scripts/ablation-replace.mjs WRAP mode, at b7ea5f21; tests import src, no dist leg): A1 only-env pick, A2 refuse-before-write (3 red), A3 login keeps env, A4 dev version, A5 live branch, A6 failed branch, A7 key runs deploy, A8 key offers login, A9 build next step, A10 everyday block, A11 per-poll wake, A12 superseded; every mutation proven on disk (anchor 1 -> 0, blob changed), every named test red, every restore proven by blob equal to HEAD, git diff HEAD and git status empty afterwards. Real `os build` from source on a minimal defineStack fixture printed the next-step line (exit 0).",
     "gates": "Local: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at b7ea5f210d derived 112 commands; all 112 run with exit captured before any pipe: 112 x exit 0. `--ran` with exit codes: '112 derived famil(ies) accounted for — 112 run, 0 NOT-MEASURED (a DERIVED zero — all 112 recorded an exit code and none of them is 3)'. An earlier run at 08361b30 caught a real finding of this diff (check:system-context-census read a field named isSystem as an elevation read; renamed to infrastructure; census OK). CI on PR head b7ea5f21 at report time: Governed Surface Queue Guard completed/success; Check Changeset, Lint · (ESLint, CI release & engine gates, PM dispatch-gates, docs & code guards, repo & tooling gates, scoped x3), Type Check · (workspace, source gates, consumer gates, debt ledger), PR-body guards, Scaffold outside the monorepo, Scaffold with repo dist, Spec property liveness, Validate Package Dependencies: in_progress; opt-in jobs skipped. Convergence is the seat's read.",
     "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
     "mcp_calls": "0 — no MCP GitHub calls",
     "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; one earlier attempt refused by write-pace before sending, nothing written) and this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/30871/comments). git pushes are not REST writes.",
     "open_questions": [
      {
       "question": "Which environment request starts a request-driven rebuild? The wait sends GET ORIGIN/api/v1/discovery (origin from data.environment.hostname) before each poll, because /api/v1/health is the container liveness probe and answers without a kernel.",
       "options": [
        "A keep /api/v1/discovery",
        "B another kernel-served path cloud#3662's author names"
       ],
       "recommendation": "A unless cloud#3662's author names a cheaper kernel-served path; the change is one line in wakeEnvironment."
      },
      {
       "question": "When data.served.desiredVersionId differs from the publish answer's id (another install replaced this deploy), the CLI reports it and exits 1 rather than reporting the other version live. Does the contract intend that reading?",
       "options": [
        "A exit 1, named as superseded (as implemented)",
        "B treat live as live regardless of desiredVersionId"
       ],
       "recommendation": "A, because a deploy that was overwritten did not go live and must not read as success."
      }
     ],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: seat (domain:skills card) · the amendment's item 5, the skill teaching `os deploy`; name and usage are in this report's summary · noted, not filed",
      "carrier: none · `os package publish --install` still prints 'Installed into environment' on the desired-state answer; by the amendment publish keeps its meaning with no wait · noted in the PR's Acceptance notes",
      "carrier: none · test/next-steps.test.ts runs `os build` in-process (about 8.8 s of test time on the shared box) and declares no test-budget line; if CI shard timings read it over 10 s it is this lane's card · noted in the PR's Acceptance notes"
     ]
    }
  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2
    os-dev-report

    {
     "issue": 30871,
     "status": "done",
     "branch": "claude/issue-30871-deploy-dev-loop",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30933",
     "session": "session_01Q4EjjRhUZjdy8VXs6Kkur2",
     "head": "c7aab69d87",
     "premise_still_valid": true,
     "summary": "Rework round for the CI red at b7ea5f21 (check:error-code-casing on packages/cli/test/deploy.test.ts:60 'kernel_warming'). Cloud origin/main b59b2748: KernelManager throws KernelWarmingError with its own code 'kernel_warming' and statusCode 503; the framework transport answers that throw with the ADR-0112 envelope 503 SERVICE_UNAVAILABLE, and 'kernel_warming' is only the declared code (packages/rest/src/rest-resolve-environment-single-window.test.ts pins that pair). So the stub now answers { success: false, error: { code: 'SERVICE_UNAVAILABLE', message } }, and the served-version.ts comment says so; no ledger exit used. Commit 6b8079a7fc, then git merge origin/main (11 commits, no file overlap, some on packages/cli generate/lint) = c7aab69d87, pushed normally. Why the red was missed before the push: check:error-code-casing is not in the `--commands` total. dispatch-gates places it among the artifact-roster families (its 21 declared literals are tracked files under packages, so 'silent' is no evidence), and --residue flags it with 'roster under packages, which one of your paths is in'. The --ran output names those 48 roster families as outside the derived total, and I ran only the total. My own os-dev rule ('new error code => check:error-code-casing') did not fire because I did not read a stub's code literal as an emission. This round ran the derived total plus every roster family --residue flags for my paths (authz-resolver, error-code-casing, filter-alias-parity).",
     "tests": "check:error-code-casing on b7ea5f21: exit 1, '✗ lowercase error-code literal(s) in a code position (ADR-0112 D1): packages/cli/test/deploy.test.ts:60 'kernel_warming' (emission)'. After the fix: exit 0, '✓ no unlisted lowercase error codes in 11554 scanned file(s) (ADR-0112)'. At c7aab69d (lock VERDICT command-exit 0, after rebuilding the cli dependency closure and create-objectstack): `pnpm --filter @objectstack/cli typecheck` exit 0; full cli unit tier `vitest run --project unit --maxWorkers=2` Test Files 393 passed (393), Tests 5528 passed (5528); `pnpm --filter create-objectstack test` Test Files 24 passed (24), Tests 370 passed (370). No behaviour changed this round (a test fixture's wire code and a comment), so no ablation.",
     "gates": "Local at c7aab69d87: `dispatch-gates --commands --repo objectstack-ai/objectstack` derived 112 (same list as at b7ea5f21), plus the 3 roster families --residue flags for my paths (check:authz-resolver, check:error-code-casing, check:filter-alias-parity) = 115 commands, every one exit 0. 4 first answered exit 3 (built output missing in the fresh worktree: skill-examples, dual-build-cjs-loads, i18n-coverage, type-check-debt); measured after a workspace build under the lock (VERDICT command-exit 0), all 4 exit 0. `--ran` with exit codes: '112 derived famil(ies) accounted for — 112 run, 0 NOT-MEASURED (a DERIVED zero — all 112 recorded an exit code and none of them is 3)'. CI on c7aab69d87, read once: 41 completed/success, 13 completed/skipped, 0 failures, 0 in progress. Lint & Repo Gates, Lint · docs & code guards, TypeScript Type Check, Test Core (8/8 shards), Build Core and Governed Surface Queue Guard all success. UI jobs skipped (no ui/ path).",
     "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
     "mcp_calls": "0",
     "api_writes": "1 — this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/30871/comments). git pushes are not REST writes. No PR body edit (the seat holds that write; replacement text is in the final report).",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": []
    }
  10. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 6, 2026
  11. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    ACCEPT → PR #30933 comment 6686781321 · 2026-11-06T14:09Z · os deploy, the d key, npm run deploy and items 1–3 landed together; ready and enqueued (Refs #30871, the seat closes this card on merge). The skill teaching os deploy (amendment item 5) is filed as a domain:skills card at close-out.

  12. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

    LANDED · 2026-11-06T14:33Z

    reading value
    PR #30933 merged 2026-11-06T14:32Z through the merge queue, merge commit 780c6c34 (Refs #30871)
    this card closed completed by the seat

    State: pm:dispatched and pm:blocking removed. os deploy, the d key in os dev, npm run deploy in the scaffold, and items 1–3 are on main. Amendment item 5 (the skill teaches it) is #30968 (domain:skills). Out of scope and still on the epic: watch mode, change preview, os mcp connect.

  13. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions