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
10 changes: 5 additions & 5 deletions .gds/bundle.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ bundle:
version: "0.9.4-dev"
release_sequence: 0
channel: "development"
source_tree_digest: "sha256:90b0640fff2f50074048eac7f73a79f385546758cc7d1d7c23424dc218eadcd6"
digest: "sha256:6c7e560ee6299259acd2fd2e1edd6eb083f9cbc8d707a36913fb69d39ffd59f8"
source_tree_digest: "sha256:a826f0d40fa915e13c75cb7bfae1dc225d556a08316a86338dacb35421505c85"
digest: "sha256:a1c9ab806577705f57cc98ca78600d2d9b5085321f144ce39255e4994e22af02"

projection:
input_digest: "sha256:09499feb56c896a377b4e041584d4db2a32537f744a91f323a0ed3efd20d73ed"
output_digest: "sha256:1412c2e0568bf067755755577cd1bf1c735b8760486e831b151b54009de75b15"
input_digest: "sha256:bd2c29dc422bb1153b6d87d7d3bba6051cfc8f5aa61d7dcf6d6abbc7981fd0ad"
output_digest: "sha256:5b9ae7bd9ea74902e1fc22dd35dadc7271ee350b3fe688051313260d3864bd54"
files:
- path: ".gds/compiled-policy.json"
digest: "sha256:37ece9020fd9eeed4ee90582600c5e50cb3a7e32ee773719d1ccfad2d9cd1b6d"
- path: ".github/workflows/gds-ci.yml"
digest: "sha256:0ab163157aef7e1ff5e76dfe240fda39f293596e252d4307c6e1700745af168f"
digest: "sha256:bba7c8fab8091083982cbb8cbe70c7802cb6b75ea5fb6038dfda137295324c0f"
4 changes: 2 additions & 2 deletions .github/workflows/gds-ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# GENERATED FILE - DO NOT EDIT DIRECTLY
# generator: gds
# bundle: 0.9.4-dev
# source-tree-digest: sha256:90b0640fff2f50074048eac7f73a79f385546758cc7d1d7c23424dc218eadcd6
# input-digest: sha256:09499feb56c896a377b4e041584d4db2a32537f744a91f323a0ed3efd20d73ed
# source-tree-digest: sha256:a826f0d40fa915e13c75cb7bfae1dc225d556a08316a86338dacb35421505c85
# input-digest: sha256:bd2c29dc422bb1153b6d87d7d3bba6051cfc8f5aa61d7dcf6d6abbc7981fd0ad
# output-digest: sha256:b9bf3d0c64c0fb371596e7d090e82e62aebbfde91929115fc15fb28644e4fd38
# edit-source:
# - .gds/repository.yaml
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Versioning.

## [Unreleased]

- Align the desktop-server device class with macos-ubuntu-bootstrap: Docker
may be none, rootful, or rootless. The bootstrap orchestrator forwards the
declared docker_mode and server-baseline hardening flags.

## [0.9.2] - 2026-09-12

- Add a public, exact-attempt harness evidence producer that rejects private
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ validated against their Git-reported superproject and typed
top-level placement drift.

A device descriptor may declare an optional `class:` block
(`profile: desktop|server`, `gui`, `docker_mode`, `execution_policy`, and
server-only `hardening`) so the device intent and the OS installer it drives
cannot disagree. The phased bootstrap orchestrator
(`profile: desktop|desktop-builds|desktop-server|server`, `gui`, `docker_mode`,
`execution_policy`, and server-baseline `hardening`) so the device intent and
the OS installer it drives cannot disagree. The phased bootstrap orchestrator
`scripts/bootstrap-device.sh` reads the class and drives the OS bootstrap, the
seed Go toolchain + `gds` build, and the control-plane staged commands in
order, with plan/apply gates at each boundary. See
Expand Down
31 changes: 14 additions & 17 deletions core/validation/device_class_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,20 @@ func TestDeviceClassAcceptsDesktopServer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
value := deviceClassFixture(t, map[string]any{
"profile": "desktop-server",
"gui": "enabled",
"docker_mode": "none",
"execution_policy": "interactive-desktop-server",
"hardening": map[string]any{"ssh": true, "ufw": true, "fail2ban": true},
})
if findings := set.Validate("device", value, "test"); len(findings) != 0 {
t.Fatalf("expected no findings, got %#v", findings)
for _, dockerMode := range []string{"none", "rootful", "rootless"} {
t.Run(dockerMode, func(t *testing.T) {
t.Parallel()
value := deviceClassFixture(t, map[string]any{
"profile": "desktop-server",
"gui": "enabled",
"docker_mode": dockerMode,
"execution_policy": "interactive-desktop-server",
"hardening": map[string]any{"ssh": true, "ufw": true, "fail2ban": true},
})
if findings := set.Validate("device", value, "test"); len(findings) != 0 {
t.Fatalf("expected no findings, got %#v", findings)
}
})
}
}

Expand Down Expand Up @@ -196,14 +201,6 @@ func TestDeviceClassRules(t *testing.T) {
},
wantCode: "GDS_DEVICE_CLASS_DESKTOP_SERVER_GUI",
},
{
name: "desktop-server with docker", osName: "linux",
class: map[string]any{
"profile": "desktop-server", "gui": "enabled", "docker_mode": "rootful",
"execution_policy": "interactive-desktop-server",
},
wantCode: "GDS_DEVICE_CLASS_DESKTOP_SERVER_DOCKER",
},
{
name: "desktop-server on macos", osName: "macos",
class: map[string]any{
Expand Down
7 changes: 5 additions & 2 deletions core/validation/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,12 @@ func deviceClassFindings(source string, object map[string]any) []domain.Finding
"The desktop-server profile requires gui enabled.",
map[string]any{"profile": profile, "gui": gui})
}
if dockerMode != "" && dockerMode != "none" {
// desktop-server defaults to none in the bootstrap contract, but
// rootful and rootless are explicit installer choices — the same
// set server accepts. Do not collapse that into desktop-builds.
if dockerMode != "" && dockerMode != "none" && dockerMode != "rootful" && dockerMode != "rootless" {
rule("GDS_DEVICE_CLASS_DESKTOP_SERVER_DOCKER",
"The desktop-server profile defaults to no local Docker; docker_mode must be none.",
"The desktop-server profile permits docker_mode none, rootful, or rootless.",
map[string]any{"profile": profile, "docker_mode": dockerMode})
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/contracts/estate-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ submodule-consistency rule above applies only to devices that carry an
inventory; there is nothing for an absent one to contradict.

A device descriptor may declare an optional `class:` block
(`profile`/`gui`/`docker_mode`/`execution_policy`, plus server-only `hardening`)
(`profile`/`gui`/`docker_mode`/`execution_policy`, plus server-baseline `hardening`)
whose vocabulary mirrors the `modules/macos-ubuntu-bootstrap` targets block, so
the descriptor's intent and the OS installer it drives cannot disagree. The
`GDS_DEVICE_CLASS_*` rules (for example `GDS_DEVICE_CLASS_MACOS_CONFLICT`,
Expand Down
2 changes: 1 addition & 1 deletion docs/runbooks/bootstrap-device.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ mirrored from `modules/macos-ubuntu-bootstrap/config/rldyour-contract.json`:
|---|---|---|
| `profile` | `desktop` \| `desktop-builds` \| `desktop-server` \| `server` | LSP-only, local-build, tunneled remote-GUI, or headless-server host |
| `gui` | `enabled` \| `disabled` | desktop-server is always `enabled`; server is always `disabled` |
| `docker_mode` | `none` \| `rootful` \| `rootless` | desktop and desktop-server require `none`; desktop-builds requires `rootful`; macOS never installs Docker |
| `docker_mode` | `none` \| `rootful` \| `rootless` | desktop requires `none`; desktop-builds requires `rootful`; desktop-server and server permit all three (desktop-server defaults to `none`); macOS never installs Docker |
| `execution_policy` | `source-lsp-only` \| `local-dev-with-builds` \| `interactive-desktop-server` \| `container-execution-only` | must match profile |
| `hardening.{ssh,ufw,fail2ban}` | `true` \| `false` | server-baseline profiles only |

Expand Down
8 changes: 4 additions & 4 deletions estate/devices/example-user-ubuntu-1.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
schema_version: 1

# A third device, and the only one that may run containers. Device class is not
# decoration: it decides what an agent harness is allowed to do here, so a
# build host and a laptop are described separately even when the estate they
# see is identical.
# A Linux workstation that may run local containers. desktop-builds is the
# GUI-optional build host; desktop-server is the tunneled remote GUI host that
# can also take an explicit Docker mode. Device class decides what an agent
# harness is allowed to do here.
device:
id: "device_01JEXAMPZ00000000000000002"
name: "example-user-ubuntu-1"
Expand Down
2 changes: 1 addition & 1 deletion schemas/v1/device.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
]
},
"docker_mode": {
"description": "Local Docker posture. desktop and desktop-server permit only none; desktop-builds requires rootful; server permits none/rootful/rootless. macOS never installs local Docker.",
"description": "Local Docker posture. desktop permits only none; desktop-builds requires rootful; desktop-server and server permit none/rootful/rootless (desktop-server defaults to none). macOS never installs local Docker.",
"enum": [
"none",
"rootful",
Expand Down
31 changes: 23 additions & 8 deletions scripts/bootstrap-device.sh
Original file line number Diff line number Diff line change
Expand Up @@ -273,23 +273,38 @@ esac
# declared, default to desktop so the orchestrator never infers a server.
PROFILE="${CLASS_PROFILE:-desktop}"
OS_ARGS=("--platform" "$PLATFORM" "--profile" "$PROFILE")
if [ "$PROFILE" = "desktop" ]; then
case "${CLASS_GUI:-enabled}" in
append_gui_flag() {
case "${CLASS_GUI:-$1}" in
enabled) OS_ARGS+=("--gui");;
disabled) OS_ARGS+=("--no-gui");;
esac
fi
if [ "$PROFILE" = "server" ]; then
# server is always headless; docker_mode defaults to rootful per contract.
OS_ARGS+=("--no-gui" "--docker-mode" "${CLASS_DOCKER:-rootful}")
# Server-only hardening toggles, if declared.
}
append_hardening_flags() {
HARDEN_SSH=$(yaml_get "$DEVICE_PATH" "device.class.hardening.ssh" || true)
HARDEN_UFW=$(yaml_get "$DEVICE_PATH" "device.class.hardening.ufw" || true)
HARDEN_F2B=$(yaml_get "$DEVICE_PATH" "device.class.hardening.fail2ban" || true)
[ "${HARDEN_SSH:-}" = "true" ] && OS_ARGS+=("--harden-ssh")
[ "${HARDEN_UFW:-}" = "true" ] && OS_ARGS+=("--enable-ufw")
[ "${HARDEN_F2B:-}" = "true" ] && OS_ARGS+=("--with-fail2ban")
fi
}
case "$PROFILE" in
desktop)
append_gui_flag enabled
;;
desktop-builds)
append_gui_flag enabled
OS_ARGS+=("--docker-mode" "${CLASS_DOCKER:-rootful}")
;;
desktop-server)
append_gui_flag enabled
OS_ARGS+=("--docker-mode" "${CLASS_DOCKER:-none}")
append_hardening_flags
;;
server)
OS_ARGS+=("--no-gui" "--docker-mode" "${CLASS_DOCKER:-rootful}")
append_hardening_flags
;;
esac

run_phase() {
local n="$1"
Expand Down
39 changes: 37 additions & 2 deletions scripts/validate_gds_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,46 @@ def _semantic_findings(schema_name: str, instance: Any, path: Path) -> list[Find
{"path": str(path), "profile": profile, "docker_mode": docker_mode},
)
)
if profile == "desktop-server":
architecture = device.get("architecture")
if os_name != "linux" or (architecture not in (None, "", "x86_64")):
findings.append(
Finding(
"GDS_DEVICE_CLASS_DESKTOP_SERVER_PLATFORM",
"high",
"The desktop-server profile requires Linux x86_64.",
{
"path": str(path),
"profile": profile,
"os": os_name,
"architecture": architecture,
},
)
)
if gui and gui != "enabled":
findings.append(
Finding(
"GDS_DEVICE_CLASS_DESKTOP_SERVER_GUI",
"high",
"The desktop-server profile requires gui enabled.",
{"path": str(path), "profile": profile, "gui": gui},
)
)
if docker_mode and docker_mode not in {"none", "rootful", "rootless"}:
findings.append(
Finding(
"GDS_DEVICE_CLASS_DESKTOP_SERVER_DOCKER",
"high",
"The desktop-server profile permits docker_mode none, rootful, or rootless.",
{"path": str(path), "profile": profile, "docker_mode": docker_mode},
)
)
# execution_policy, when declared, must match the profile.
if execution_policy and profile:
_POLICY_MAP = {
"desktop": "source-lsp-only",
"desktop-builds": "local-dev-with-builds",
"desktop-server": "interactive-desktop-server",
"server": "container-execution-only",
}
expected = _POLICY_MAP.get(profile)
Expand All @@ -599,13 +634,13 @@ def _semantic_findings(schema_name: str, instance: Any, path: Path) -> list[Find
isinstance(hardening, Mapping)
and hardening
and profile
and profile != "server"
and profile not in {"server", "desktop-server"}
):
findings.append(
Finding(
"GDS_DEVICE_CLASS_HARDENING_PROFILE",
"high",
"Device class hardening is only permitted with the server profile.",
"Device class hardening is only permitted with a server-baseline profile.",
{"path": str(path), "profile": profile, "hardening": hardening},
)
)
Expand Down
11 changes: 6 additions & 5 deletions skills/canonical/gds-orient/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,15 @@ anchoring, materializing, or reclassifying it.

The optional `class:` block on a device descriptor
(`estate/devices/<device>.yaml`) expresses device-class intent —
`profile: desktop|desktop-builds|server`, `gui: enabled|disabled`,
`profile: desktop|desktop-builds|desktop-server|server`, `gui: enabled|disabled`,
`docker_mode: none|rootful|rootless`, and
`execution_policy: source-lsp-only|local-dev-with-builds|container-execution-only`.
`execution_policy: source-lsp-only|local-dev-with-builds|interactive-desktop-server|container-execution-only`.
It mirrors the `macos-ubuntu-bootstrap` targets block so the device descriptor
and the OS installer it drives cannot disagree. Cross-field rules are enforced
by the schema validator (`GDS_DEVICE_CLASS_*`): `desktop` permits only
`docker_mode: none`, `desktop-builds` requires `rootful`, and `server` is
always `gui: disabled`. The phased bootstrap orchestrator that consumes the
class is `scripts/bootstrap-device.sh`; see
`docker_mode: none`, `desktop-builds` requires `rootful`, `desktop-server`
permits `none|rootful|rootless` (default `none`) with `gui: enabled`, and
`server` is always `gui: disabled`. The phased bootstrap orchestrator that
consumes the class is `scripts/bootstrap-device.sh`; see
`docs/runbooks/bootstrap-device.md` for the seam. Otherwise no additional
runtime reference is required; use current structured `gds` output.
6 changes: 6 additions & 0 deletions tests/fixtures/schemas/v1/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,12 @@
"path": "valid-device-class-desktop.yaml",
"valid": true
},
{
"id": "valid-device-class-desktop-server",
"schema": "device",
"path": "valid-device-class-desktop-server.yaml",
"valid": true
},
{
"id": "invalid-device-class-server-gui",
"schema": "device",
Expand Down
30 changes: 30 additions & 0 deletions tests/fixtures/schemas/v1/valid-device-class-desktop-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
schema_version: 1

device:
id: "device_0Q0MPJ4Z2ENZ97XWETRESKZGTH"
name: "example-ubuntu-desktop-server"
os: "linux"
architecture: "x86_64"
class:
profile: "desktop-server"
gui: "enabled"
docker_mode: "rootful"
execution_policy: "interactive-desktop-server"

workspace_roots:
personal: "${HOME}/Developer/personal"
organization: "${HOME}/Developer/organization"

materialization:
default_mode: "absent"
include:
- selector: "portfolio:active-projects"
workspace_root: "personal"
mode: "active"

harnesses:
- "codex"
- "claude-code"

state:
path: "${XDG_STATE_HOME}/github-device-sync"
8 changes: 8 additions & 0 deletions tests/test_bootstrap_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def test_source_build_never_accepts_a_merely_runnable_binary() -> None:
assert "if ! source_build_dirty" in script


def test_orchestrator_forwards_class_docker_mode() -> None:
script = BOOTSTRAP.read_text(encoding="utf-8")
assert 'desktop-server)' in script
assert 'OS_ARGS+=("--docker-mode" "${CLASS_DOCKER:-none}")' in script
assert 'OS_ARGS+=("--docker-mode" "${CLASS_DOCKER:-rootful}")' in script
assert "append_hardening_flags" in script


def test_seed_go_verifies_the_physical_toolchain_without_auto_selection() -> None:
script = BOOTSTRAP.read_text(encoding="utf-8")

Expand Down