@devrouter/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5347,7 +5347,7 @@ function retain(session, previous) {
5347
5347
 
5348
5348
  // src/core/reliability-operation-store.ts
5349
5349
  function installedCliVersion() {
5350
- return true ? "0.1.0" : "0.0.0-dev";
5350
+ return true ? "0.1.1" : "0.0.0-dev";
5351
5351
  }
5352
5352
  function compareCliVersions(left, right) {
5353
5353
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7163,7 +7163,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
7163
7163
  const config = parseConfig(parsed ?? {}, configPath);
7164
7164
  const requiredVersion = config.devrouter?.version;
7165
7165
  if (requiredVersion && !hasWarnedVersionMismatch) {
7166
- const cliVersion = true ? "0.1.0" : "0.0.0-dev";
7166
+ const cliVersion = true ? "0.1.1" : "0.0.0-dev";
7167
7167
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7168
7168
  hasWarnedVersionMismatch = true;
7169
7169
  process.stderr.write(
@@ -11957,6 +11957,11 @@ function reliabilityRecovery(reason2, repoPath) {
11957
11957
  case "stop-incomplete":
11958
11958
  case "operation-unknown":
11959
11959
  return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
11960
+ case "start-refused":
11961
+ return [
11962
+ `Run: devrouter doctor ${repoPath} to read the refused fixed host-port claim and its holder`,
11963
+ `Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${repoPath}`
11964
+ ];
11960
11965
  case "unadmittable":
11961
11966
  return [
11962
11967
  `Run: devrouter stop ${repoPath}`,
@@ -11970,6 +11975,13 @@ function reliabilityRecovery(reason2, repoPath) {
11970
11975
  return [];
11971
11976
  }
11972
11977
  }
11978
+ function refusedManagedStart(result) {
11979
+ if (result?.ok !== true) return false;
11980
+ const value = result.value;
11981
+ if (typeof value !== "object" || value === null) return false;
11982
+ const conflicts = value.hostPortConflicts;
11983
+ return Array.isArray(conflicts) && conflicts.length > 0;
11984
+ }
11973
11985
  function readReliabilityStatus(options) {
11974
11986
  let provider;
11975
11987
  try {
@@ -11989,7 +12001,7 @@ function readReliabilityStatus(options) {
11989
12001
  return void 0;
11990
12002
  }
11991
12003
  if (!record5) return void 0;
11992
- const reason2 = reliabilityAttention(record5.state);
12004
+ const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
11993
12005
  return {
11994
12006
  desired: record5.state.desired,
11995
12007
  phase: record5.state.phase,
package/dist/devrouter.js CHANGED
@@ -4275,7 +4275,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4275
4275
  const config = parseConfig(parsed ?? {}, configPath);
4276
4276
  const requiredVersion = config.devrouter?.version;
4277
4277
  if (requiredVersion && !hasWarnedVersionMismatch) {
4278
- const cliVersion = true ? "0.1.0" : "0.0.0-dev";
4278
+ const cliVersion = true ? "0.1.1" : "0.0.0-dev";
4279
4279
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4280
4280
  hasWarnedVersionMismatch = true;
4281
4281
  process.stderr.write(
@@ -6259,7 +6259,7 @@ var init_reliability_contract = __esm({
6259
6259
 
6260
6260
  // src/core/reliability-operation-store.ts
6261
6261
  function installedCliVersion() {
6262
- return true ? "0.1.0" : "0.0.0-dev";
6262
+ return true ? "0.1.1" : "0.0.0-dev";
6263
6263
  }
6264
6264
  function compareCliVersions(left, right) {
6265
6265
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -15902,6 +15902,11 @@ function reliabilityRecovery(reason, repoPath) {
15902
15902
  case "stop-incomplete":
15903
15903
  case "operation-unknown":
15904
15904
  return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
15905
+ case "start-refused":
15906
+ return [
15907
+ `Run: devrouter doctor ${repoPath} to read the refused fixed host-port claim and its holder`,
15908
+ `Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${repoPath}`
15909
+ ];
15905
15910
  case "unadmittable":
15906
15911
  return [
15907
15912
  `Run: devrouter stop ${repoPath}`,
@@ -15915,6 +15920,13 @@ function reliabilityRecovery(reason, repoPath) {
15915
15920
  return [];
15916
15921
  }
15917
15922
  }
15923
+ function refusedManagedStart(result) {
15924
+ if (result?.ok !== true) return false;
15925
+ const value = result.value;
15926
+ if (typeof value !== "object" || value === null) return false;
15927
+ const conflicts = value.hostPortConflicts;
15928
+ return Array.isArray(conflicts) && conflicts.length > 0;
15929
+ }
15918
15930
  function readReliabilityStatus(options) {
15919
15931
  let provider;
15920
15932
  try {
@@ -15934,7 +15946,7 @@ function readReliabilityStatus(options) {
15934
15946
  return void 0;
15935
15947
  }
15936
15948
  if (!record5) return void 0;
15937
- const reason = reliabilityAttention(record5.state);
15949
+ const reason = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
15938
15950
  return {
15939
15951
  desired: record5.state.desired,
15940
15952
  phase: record5.state.phase,
@@ -21024,7 +21036,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
21024
21036
  - **TLS**: namespaced hosts (\`web.<ws>.localhost\`) are not covered by the \`*.localhost\` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.
21025
21037
  - **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
21026
21038
  - **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once per installed Devsy release, rerunning it after a Devsy update, so Devrouter acquires and verifies the official Linux agent in its own machine cache. Devrouter supports Devsy releases in the range >=1.16.2 <2.0.0 and refuses a release whose published asset has no SHA-256 digest. \`doctor\` reports \`ready\`, \`missing\`, \`stale\`, or \`invalid\` without network access, and \`ensure\` fails before the provider queue when readiness is not \`ready\`. An explicit \`DEVSY_AGENT_BINARY\` remains authoritative and must match the official asset for the installed release.
21027
- - **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
21039
+ - **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. A start refused before any provider mutation \u2014 a fixed published host port already held by a running container \u2014 stays visible in \`status\` as a \`start-refused\` attention reason naming the read-only \`doctor\` check and the consumer-side fix; an explicit \`stop\` releases the intent and the reason, and a later admitted ensure clears it. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
21028
21040
  - **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
21029
21041
  - **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
21030
21042
  - **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
@@ -21299,7 +21311,7 @@ function buildOnboardingPrompt(options = {}) {
21299
21311
  "- if kind=app and runtime=proxy:",
21300
21312
  " - HTTP-only optional readiness: { path: /api/health, statuses: [200], contentType: application/json }; statuses default to [200], explicit lists allow unique 2xx/4xx codes, never redirects. Paths stay on the configured host with no query, fragment, percent escapes, backslash or dot segments. Media types match their case-insensitive base type.",
21301
21313
  " - Declared application readiness failures return applicationReadiness.status=application-error and a nonzero ensure exit, while retaining tooling and routes for application fixes; do not reset caches or recreate the provider. Absent readiness means legacy route liveness, not full application readiness.",
21302
- " - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. Unverifiable evidence also refuses.",
21314
+ " - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. The refused start stays visible in `devrouter status` as a `start-refused` attention reason that names the read-only `doctor` check and the consumer-side fix; an explicit `stop` releases the intent and the reason, and a later admitted ensure clears it. Unverifiable evidence also refuses.",
21303
21315
  ' - upstream: "host:port" (an already-running port, e.g. a devcontainer published on 127.0.0.1:3000, or a container reachable by name on a shared Docker network such as `derivatives-db:5432`)',
21304
21316
  ` - upstream may use the \`${WORKSPACE_PLACEHOLDER}\` placeholder (e.g. \`${WORKSPACE_PLACEHOLDER}-app:3000\`) to target a per-workspace devcontainer alias; it is substituted with the resolved workspace token at runtime and re-validated. Do NOT put \`${WORKSPACE_PLACEHOLDER}\` in \`host\` (rejected) \u2014 the host is auto-namespaced.`,
21305
21317
  " - managed `devrouter ensure` requires every HTTP/TCP proxy upstream to begin with the exact resolved workspace/project alias prefix before it mutates DevPod or routes",
@@ -23193,7 +23205,7 @@ function probeInstalledCliVersion(executable) {
23193
23205
  }
23194
23206
  function buildCliPathCheck(options = {}) {
23195
23207
  const id5 = "global.cli-path";
23196
- const runningVersion = options.runningVersion ?? (true ? "0.1.0" : "0.0.0-dev");
23208
+ const runningVersion = options.runningVersion ?? (true ? "0.1.1" : "0.0.0-dev");
23197
23209
  if (runningVersion === "0.0.0-dev") {
23198
23210
  return {
23199
23211
  id: id5,
@@ -24041,7 +24053,7 @@ async function buildDoctorReport(options = {}) {
24041
24053
  suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
24042
24054
  });
24043
24055
  }
24044
- const cliVersion = true ? "0.1.0" : "0.0.0-dev";
24056
+ const cliVersion = true ? "0.1.1" : "0.0.0-dev";
24045
24057
  const configVersion = config.devrouter?.version;
24046
24058
  const installedKnown = cliVersion !== "0.0.0-dev";
24047
24059
  const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
@@ -30021,7 +30033,7 @@ var init_version = __esm({
30021
30033
 
30022
30034
  // src/cli.ts
30023
30035
  var import_commander = require("commander");
30024
- var CLI_VERSION = true ? "0.1.0" : "0.0.0-dev";
30036
+ var CLI_VERSION = true ? "0.1.1" : "0.0.0-dev";
30025
30037
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
30026
30038
  function withErrorHandling(action2) {
30027
30039
  return async (...args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrouter/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local dev routing CLI with shared Traefik reverse proxy",
5
5
  "author": "Roland Schlaefli",
6
6
  "homepage": "https://github.com/rschlaefli/devrouter#readme",
@@ -54,6 +54,7 @@
54
54
  "qualify:harness-journey": "./scripts/qualify-harness-journey.sh",
55
55
  "qualify:codex-journey": "DR_JOURNEY_HARNESS=codex ./scripts/qualify-harness-journey.sh",
56
56
  "qualify:non-node": "./scripts/qualify-non-node-consumer.sh",
57
+ "qualify:slow-dependency": "./scripts/qualify-slow-dependency-recovery.sh",
57
58
  "test:process": "bash scripts/test-devrouter-process.sh",
58
59
  "check": "biome check .",
59
60
  "check:fix": "biome check --write .",
@@ -0,0 +1,48 @@
1
+ # Upgrade to devrouter 0.1.1
2
+
3
+ This is a patch release on top of 0.1.0. It closes one way a managed environment
4
+ could sit blocked without saying so: a start that admission refused on a fixed
5
+ published host-port claim left the journal recording the intent as `running`
6
+ while no runtime existed, so `devrouter status` reported a stable, drifting-free
7
+ environment that could not progress.
8
+
9
+ ## What changed for a consumer
10
+
11
+ - A managed `ensure` refused because a running container already holds a fixed
12
+ published host port now stays visible after the command exits. `devrouter
13
+ status` reports a `start-refused` attention reason with two recovery
14
+ commands: the read-only `devrouter doctor <repo>` check that names the
15
+ conflicting consumer binding and its holder, and the consumer-side fix
16
+ followed by `devrouter ensure <repo>`.
17
+ - An explicit `devrouter stop <repo>` releases that intent and the reason, and a
18
+ later admitted ensure clears it. Nothing else about the refusal changed: the
19
+ start is still refused before any provider mutation, and live-worker,
20
+ unknown-ownership and surviving-resource checks still refuse fail-closed.
21
+ - The release also carries `pnpm qualify:slow-dependency`, the developer
22
+ qualifier behind the Q06 dependency-recovery evidence. It is a repository
23
+ script and has no consumer-facing effect.
24
+
25
+ ## Task
26
+
27
+ Use the published 0.1.1 CLI. Update the repository `devrouter.version` metadata
28
+ only when consumer source changes are authorized, and refresh generated agent
29
+ guidance only with its owner's authority. No schema, command, flag, environment
30
+ or journal migration is needed, and the upgrade rewrites no runtime state.
31
+
32
+ ## Validation
33
+
34
+ Verify the resolved executable and installed version, then run `devrouter
35
+ doctor` for the checkout and confirm `global.cli-path` reports one
36
+ authoritative install. Run `devrouter status` and confirm the reported intent,
37
+ phase and attention reason agree with the runtime you observe. When the reported
38
+ reason is `start-refused`, resolve the named binding as the owner of that
39
+ port — the shared router is never the side to stop — and keep each lifecycle
40
+ operation's own terminal evidence separate from a status block or progress line.
41
+
42
+ ## Report
43
+
44
+ Report the release revision, the executable and installed version, the status
45
+ block for any affected checkout, and the terminal result of any authorized
46
+ lifecycle operation. Keep source and package validation separate from consumer
47
+ readiness or recovery, and record unknown ownership or surviving resources as
48
+ unresolved rather than as recovered.