Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ These files exist upstream, but J5 owns its copy outright and they are not integ

### Sanctioned appended integration cases

Against upstream pin `29980a31409234b676f97bd477c4e46fdb61a929`, the inventory has 65 entries: 1–64 plus 15b. Fifteen are retired and keep their numbers (10–14, 15b, 16–18, 20, 27–29, 54, 55), and 19 and 39 are retired except for a remainder. Existing numbers never change; a new case takes the next number. Line coordinates and landing SHAs below describe historical integrations; use named symbols and tests after upstream movement. Branding has its own inventory in `BRANDING.md`; temporary defect patches are listed separately below.
Against upstream pin `29980a31409234b676f97bd477c4e46fdb61a929`, the inventory has 68 entries: 1–67 plus 15b. Fifteen are retired and keep their numbers (10–14, 15b, 16–18, 20, 27–29, 54, 55), and 19 and 39 are retired except for a remainder. Existing numbers never change; a new case takes the next number. Line coordinates and landing SHAs below describe historical integrations; use named symbols and tests after upstream movement. Branding has its own inventory in `BRANDING.md`; temporary defect patches are listed separately below.

1. A1's independent communication-ledger migration lane: in `apps/server/src/persistence/Sqlite.ts`, `layerSetup` runs `runJ5CompatibleUpstreamMigrations` and then `runJ5A2AMigrations` where upstream calls `runMigrations()`. Upstream's `layerConfig` has already run `initializeV2Database`, which copies `state.sqlite` to `statev2.sqlite`, so both run only against `statev2.sqlite`. J5 adopts that copy-on-first-start unchanged (Jackson's 2026-09-24 decision #2): an existing `statev2.sqlite` wins and `state.sqlite` is never recopied or written again, so the cutover is one-way: writes after the first V2 boot are invisible to an older binary. Snapshot before the upgrade, and roll back across it by stopping writers and moving `statev2.sqlite` aside (`docs/j5/runbooks/dogfood-runtime.md`); never run old and new binaries against the two files. `layerFromPath` calls one J5 step, `snapshotBeforeMigrations` (`apps/server/src/j5/persistence/MigrationSnapshot.ts`), before it builds the SQL layer, so every file-backed open is covered. When the database has migration history and either lane has a migration pending (upstream's `effect_sql_migrations` against upstream's manifest, or `j5_a2a_migrations` against J5's ledger list), it is first copied with `VACUUM INTO`, through a read-only connection, to `statev2.pre-migration-u<upstream id>-j<J5 id>.sqlite` beside it, named for the newest migration each lane had recorded. The copy is synchronous and does not use `node:sqlite`'s `backup()`: on the Node 26 that release executables embed, `backup()` rejects the one-step rate Node 24 accepts and its promise settles only when another event wakes the loop, which stopped the first nightly from starting. The snapshot is a compacted copy in rollback-journal mode; the server sets WAL again when it opens a restored one. A database with no history, or with nothing pending, is not copied. The newest three snapshots are kept (`MIGRATION_SNAPSHOTS_KEPT`) and older ones deleted. This is the roll-forward policy's emergency exit (issue #507): J5 adds no refusal to start on a database newer than the build, as upstream adds none. A failed copy dies there, so the server does not start and nothing migrates. It dies rather than fails so the layer's error type, which upstream's tests name, stays upstream's. `MigrationSnapshot.test.ts` covers it. The `j5_run_observability_migrations` lane (one index) is not consulted. J5 tooling (`fleet-load.ts`, `devDeliverySeed.ts`, `dogfood-snapshot.sh`, `dogfood-local.sh`) targets `statev2.sqlite`; `apps/server/src/j5/persistence/StateV2Cutover.test.ts` covers the copy, no-recopy and failed-first-boot retry. Case 31 defines the compatibility wrapper; J5's own migration IDs remain in separate tracking tables and run after upstream migration completion.
2. The authenticated J5 MCP registration seam: the `layer` of `apps/server/src/mcp/McpHttpServer.ts` calls `layerJ5Toolkits(toolkitRegistration)` (`apps/server/src/j5/a2a/mcp/registration.ts`) in place of upstream's orchestrator, thread, attachment, project and environment registrations. Upstream's preview, preview-controls, worktree, pull-request, device and html toolkits are registered whole, as upstream registers them. Upstream's lint rule `no-raw-mcp-registration` allows MCP registration only in `McpHttpServer.ts`, so J5 registers through the `toolkitRegistration` function that file passes in. `layerJ5Toolkits` registers J5's own tools and the admitted part of those five upstream toolkits. `upstreamCatalog.ts` selects the admitted tools one by one and runs them on upstream's own handlers through `handlersFor`, which is one documented cast. `apps/server/src/mcp/McpInvocationContext.ts` exports `ALL_MCP_CAPABILITIES` and adds the `artifacts` capability that case 33's tools check; nothing imports the export at pin `29980a3140`.
Expand Down Expand Up @@ -412,6 +412,8 @@ migrations after upstream migrations.

66. Another worktree's install doesn't restart a dev server ([#544](https://github.com/Jacksondr5/j5code/issues/544), Jackson's 2026-10-11 approval). The `dev` script in `apps/server/package.json` is `node --watch --watch-path=src --watch-path=../../packages src/bin.ts`; upstream's is `node --watch src/bin.ts`. Without a path, Node on Linux watches every file the server has loaded, including about 1,200 under `node_modules/.pnpm`. pnpm hardlinks those from its shared store, so another worktree's install or removal changes their link count, inotify reports it, and the server restarts. With the two paths, Node watches `apps/server/src` and `packages` as directory trees: it enters each package's own `node_modules` but stops at the symlinks, so no store file is watched. Two things follow. A file outside those trees no longer restarts the server: of the files the server loads, that is `scripts/lib/j5-branding.ts` and `apps/server/package.json`. And every file inside them does, loaded or not: a test file, or `packages/client-runtime`, which the server doesn't import. `dev:desktop` is not affected, because it runs the built `apps/server/dist/bin.mjs` under its own watcher. This is a candidate to offer upstream ([#276](https://github.com/Jacksondr5/j5code/issues/276)). On a pin advance, keep the two `--watch-path` flags if upstream still runs a bare `--watch`, and drop the case if upstream narrows the watch itself.

67. A server's name can be set ([#545](https://github.com/Jacksondr5/j5code/issues/545), register D32, Jackson's 2026-10-11 approval). Upstream's `resolveServerEnvironmentLabel` in `apps/server/src/environment/ServerEnvironmentLabel.ts` names a server after its machine, so two servers on one host report the same name. The file carries one import and one early return at the top of that function: it reads J5-owned `apps/server/src/j5/serverName.ts` (`configuredServerName`, an Effect `Config` read of `J5CODE_SERVER_NAME`), passes the value through upstream's own `normalizeLabel`, and returns it when it is not blank. Unset or blank falls through to upstream's lookups unchanged. The variable is `J5CODE_`-prefixed because it is J5's own, like `J5CODE_TELEMETRY_TAG` (case 63); if upstream takes the change it would be a `T3CODE_` name there. `ServerEnvironmentLabel.test.ts` adds two tests (the variable wins; a blank value is ignored) and gives every test in the file an empty config provider through `withHostPlatform`, because a named server's variable reaches the agents it runs. On a pin advance, keep the early return ahead of upstream's first lookup.

## Pin and upstream advance runbook

Current pin: `29980a31409234b676f97bd477c4e46fdb61a929`, from upstream `main` (frozen 2026-10-08). The previous pin was `67a2be0fdbee7afb64b691f147ed286a108c706b`, from upstream's V2 branch `t3code/codex-turn-mapping`.
Expand Down Expand Up @@ -687,6 +689,8 @@ explicitly marked.
| `apps/server/src/entrypoint.test.ts` | N | T |
| `apps/server/src/environment/ServerEnvironment.test.ts` | N | 34, 48 |
| `apps/server/src/environment/ServerEnvironment.ts` | N | 34, 48 |
| `apps/server/src/environment/ServerEnvironmentLabel.test.ts` | N | 67 |
| `apps/server/src/environment/ServerEnvironmentLabel.ts` | N | 67 |
| `apps/server/src/http.ts` | R | B |
| `apps/server/src/mcp/McpHttpServer.ts` | R | 2 |
| `apps/server/src/mcp/OrchestratorMcpService.ts` | A | Saved-agent mentions |
Expand Down
38 changes: 38 additions & 0 deletions apps/server/src/environment/ServerEnvironmentLabel.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it } from "@effect/vitest";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -49,9 +50,13 @@ const withHostPlatform = <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>,
platform: NodeJS.Platform,
hostname: string,
env: Readonly<Record<string, string>> = {},
) =>
Layer.mergeAll(
layer,
// The server's own environment reaches the agents it runs, so a named server's
// J5CODE_SERVER_NAME must not leak into these tests.
ConfigProvider.layer(ConfigProvider.fromEnv({ env })),
Layer.succeed(HostProcessPlatform, platform),
Layer.succeed(HostProcessHostname, hostname),
);
Expand Down Expand Up @@ -286,4 +291,37 @@ describe("resolveServerEnvironmentLabel", () => {
expect(result).toBe("t3code");
}),
);

it.effect("uses J5CODE_SERVER_NAME ahead of the host's friendly name", () =>
Effect.gen(function* () {
const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
cwdBaseName: "t3code",
}).pipe(
Effect.provide(
withHostPlatform(layerLinuxMachineInfo, "linux", "buildbox", {
J5CODE_SERVER_NAME: " Remote ",
}),
),
);

expect(result).toBe("Remote");
expect(runMock).not.toHaveBeenCalled();
}),
);

it.effect("ignores a blank J5CODE_SERVER_NAME", () =>
Effect.gen(function* () {
const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
cwdBaseName: "t3code",
}).pipe(
Effect.provide(
withHostPlatform(layerLinuxMachineInfo, "linux", "buildbox", {
J5CODE_SERVER_NAME: " ",
}),
),
);

expect(result).toBe("Build Agent 01");
}),
);
});
6 changes: 6 additions & 0 deletions apps/server/src/environment/ServerEnvironmentLabel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as FileSystem from "effect/FileSystem";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";

import { configuredServerName } from "../j5/serverName.ts";
import * as ProcessRunner from "../processRunner.ts";

interface ResolveServerEnvironmentLabelInput {
Expand Down Expand Up @@ -182,6 +183,11 @@ const resolveFriendlyHostLabel = Effect.fn("resolveFriendlyHostLabel")(function*
export const resolveServerEnvironmentLabel = Effect.fn("resolveServerEnvironmentLabel")(function* (
input: ResolveServerEnvironmentLabelInput,
) {
const configuredName = normalizeLabel(yield* configuredServerName);
if (configuredName) {
return configuredName;
}

const friendlyHostLabel = yield* resolveFriendlyHostLabel();
if (friendlyHostLabel) {
return friendlyHostLabel;
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/j5/serverName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";

/**
* The name a person gave this server with `J5CODE_SERVER_NAME`, or null when the variable is
* unset. `resolveServerEnvironmentLabel` reads it before the host's own names, so two servers on
* one machine can be told apart. The caller trims it and treats a blank value as unset.
*/
export const configuredServerName = Config.String("J5CODE_SERVER_NAME").pipe(
Config.option,
Config.map(Option.getOrNull),
Effect.orElseSucceed(() => null),
);
13 changes: 13 additions & 0 deletions docs/j5/product/upstream.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ Each advance checks for new upstream reads of `T3CODE_HOME` or `.t3` paths. The

**Decided:** Jackson, 2026-10-11. Recorded in FORK.md's temporary patches.

#### D32. A server's name can be set with `J5CODE_SERVER_NAME`

**Upstream:** a server names itself after its machine: the host's friendly name, then its hostname, then the folder it was started in. Nothing overrides that, so two servers on one machine report the same name.

**J5:** when `J5CODE_SERVER_NAME` is set and not blank, it is the server's name. Unset or blank, the server names itself as upstream's does.

**Why:** in a two-server peering test on one host both servers were `openclaw`, so neither agents nor people could tell which participant lived on which server. Giving the second server its own hostname namespace was rejected: the process looks like root inside one, which provider CLIs and the browser sandbox may react to. We need agents to be able to reliably test these scenarios.

**Consequences:** one import and one early return in `ServerEnvironmentLabel.ts` (`FORK.md` case 67). The name is read once, when the server starts. Processes a server starts inherit its environment, so a server an agent starts from a named server takes the same name unless the variable is unset or set again. There is no setting or flag, only the environment variable. Upstream has the same clash, so this is a candidate to offer back ([#276](https://github.com/Jacksondr5/j5code/issues/276)).

**Decided:** Jackson, 2026-10-11 ([#545](https://github.com/Jacksondr5/j5code/issues/545)). Recorded in FORK.md case 67.

### Awaiting a decision

These already diverge on `j5/main`, but no human ruling is on record. Each landed as an implementer's call inside a merged PR. The person rules on each one; an approved entry moves up into its section, and a rejected one becomes a fix.
Expand Down Expand Up @@ -422,3 +434,4 @@ J5 kept a run from resuming after a usage-limit reset when the person had presse
- 2026-10-08 — the advance onto upstream `main`: D5, D7 and D23 retired, D15 recorded as retired, D8's retired text corrected, and D1, D2, D3, D4, D6, D14, D19, D25 and D28 rewritten for what upstream now ships (Jackson's decisions of that day).
- 2026-10-09 — D30 added: a message from another participant holds a merged thread open (Jackson).
- 2026-10-09 — D1 rewritten: J5's instruction text lives in upstream's shared provider package and reaches Pi (Jackson).
- 2026-10-11 — D32 added: a server's name can be set with `J5CODE_SERVER_NAME` (Jackson, [#545](https://github.com/Jacksondr5/j5code/issues/545)).
Loading