You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Close the gap described in docs/todo.md: network_mode: host and network_mode: none are accepted by the config schema and documented in crates/jiji-config/src/jiji.yml, but no runtime code path reads either value (container_runtime::build_dynamic_run always renders normal project bridge networking). A service configured with either value silently gets bridge networking instead, which is a correctness gap: the config the user wrote does not describe the container that actually runs.
Decision: implement host with a small, well-defined scope; reject none during validation with an actionable error. Rationale below.
Current state (confirmed by reading the code, not the docs)
Service.network_mode: String (crates/jiji-config/src/schema.rs), any string is accepted except a container: prefix, which validation already rejects (UNSUPPORTED_NETWORK_MODE) in favor of service:<name>.
Validation already gates non-bridge modes generically: replicas > 1 requires bridge (NON_BRIDGE_SCALE), and proxy: requires bridge (NON_BRIDGE_PROXY). These two rules already apply to host/none today and need no change.
Service.network_mode_dependency() only matches a service: prefix, so host/none never take that branch anywhere.
The only runtime path a healthcheck: can be attached to is a proxy target (ProxyTarget/TCP target healthcheck: Option<HealthcheckConfig> in schema.rs). Since proxy: is already forbidden for non-bridge modes, host/none can never carry an HTTP/command healthcheck today -- deploy_transaction.rs always falls back to the engine-native container-readiness check ({engine} inspect ... State.Status == running) for them, by construction. This removes most of the apparent complexity: there is no HTTP health-check path to design for either mode in this round.
crates/jiji-network/src/service_runtime.rs::NetworkedContainerRun has exactly two shapes today: dynamic (bridge, leased --ip, --dns*) and shared (--network container:<target>, no addressing flags at all, used for network_mode: service:<name>). args() matches on shared_with_container: Option<String>.
deploy_transaction.rs::deploy_endpoint already branches on ctx.service.network_mode_dependency() to choose deploy_shared_endpoint (no address lease, reuses the upstream's address) vs. deploy_dynamic_endpoint (leases a fresh address per deployment via the agent's AllocateAddress RPC). host needs a third branch of the same shape.
CatalogRecord.address: Ipv4Addr is non-optional and is what jiji-agent/src/dns.rs publishes as the .jiji DNS answer. Making it optional would be a distributed catalog schema change (replicated, version-checked between agents per docs/architecture-notes.md#distributed-control-plane) -- far more invasive than this gap justifies, and none mode has no address to put there under any design.
ServerPlan.management_address (crates/jiji-network/src/planner.rs) already exists: the server's own WireGuard mesh address, computed deterministically, no lease required. This is the natural address for a host-mode catalog record -- the container shares the host's entire network namespace, including the WireGuard interface, so anything the container binds becomes reachable at management_address:<port> from the rest of the mesh, and at the host's own address from outside it.
commands/service/remove.rs's ReleaseAddress call is already unconditional (no "is this a dependent" check) and is already exercised today for service:<name> dependents, which never held a lease. The agent's release handler already tolerates "nothing to release." No change needed there for host.
Confirmed live on this machine (Docker 29.3.0, Podman 6.0.2): -p combined with --network host is a discarded-with-warning on both engines, not a hard error (docker: "WARNING: Published ports are discarded when using host network mode", exit 0; podman: "Port mappings have been discarded because "host" network namespace mode does not support them", exit 0). Older Podman releases have been reported to hard-error on this combination instead of warning, so treat "never render -p for a host-mode container" as a hard requirement regardless -- relying on either engine's discard-and-warn behavior would still spam noisy warnings into every deploy's output and is not something to depend on across engine versions.
Confirmed live on this machine (Docker 29.3.0, Podman 6.0.2): --dns 9.9.9.9 --dns-search example.internal --dns-option ndots:1 combined with --network host works correctly on both engines -- each writes the requested /etc/resolv.conf into the container despite it sharing the host's network namespace (verified via cat /etc/resolv.conf inside the container). .jiji resolution for a host-mode container is safe to design around; no open question here.
container_ops::create_and_start currently reconciles the project bridge's Podman DNS address only when run.shared_with_container.is_none(). A host-mode container has no bridge attachment either and needs the same skip.
Why reject none instead of implementing it
network_mode: none gives a container no network at all. Every invariant this project relies on assumes a service has some reachable address:
DNS only ever publishes an active+healthy record with a real address (docs/architecture-notes.md, "Non-negotiable invariants").
The catalog's address field is not optional, and making it optional is a replicated-schema change, not a container_runtime.rs change.
The one thing none mode is actually good for -- an isolated one-off or scheduled task that talks to nothing -- is already served by crons:, which run in their own independently-addressed container regardless of the owning service's network_mode (cron_exec.rs).
There is no address to publish, no way to health-check beyond "process exists" (already true for host too, so that alone isn't disqualifying), and no realistic caller need that service:<name> (explicit sharing) or plain bridge (default isolation) doesn't already cover. Implementing it would mean carrying a permanently-address-less code path through the catalog, DNS, and reconciliation layers for a case with no real use. Rejecting it during validation is a two-line change and leaves the door open to revisit if a real need shows up.
Design: network_mode: host
Config surface
ports: becomes at most one entry, a bare container-side port number only ("8080"), never a host:container mapping and never a /udp suffix (both are meaningless once the container shares the host's ports directly -- the app must already bind the port it wants). This entry is never rendered as -p; it exists purely so jiji knows what the service is listening on, for the per-server uniqueness check below and for any future host-mode healthcheck-by-port work.
proxy: and replicas > 1 stay rejected by the existing generic non-bridge checks (NON_BRIDGE_PROXY, NON_BRIDGE_SCALE); no change.
New validation, mirroring validate_tcp_targets's per-project seen_listen_ports pattern but keyed by (server, port) instead of just port: two host-mode services whose servers: sets overlap must not declare the same port. Actionable error naming both services and the shared server.
Document plainly (jiji.yml + website) that this check is project-scoped only: jiji cannot see another project's host-mode services on a shared machine, so a cross-project port collision is only caught at container start (bind: address already in use), surfacing as a stuck "starting container" / failed health check the same way any other host-resource conflict does today. This is the same limitation the raw TCP proxy section already discloses for its own port space (docs/architecture-notes.md#shared-ingress-proxy).
A host-mode service still allows volumes:/files:/directories:, devices:, privileged:, cap_add:, cpus:/memory:/gpus:, and crons: -- none of those interact with networking, so none of the existing rules for them need to change.
Add a validation warning (not an error) when network_mode: host is combined with environment.secrets/typical "protect this" config, or simply document the caveat instead of a warning: host networking removes jiji's mesh isolation for that container -- it can reach (and be reached by) anything the host's own network can, bypassing the project bridge entirely. Land this as documentation; do not invent a new warning mechanism just for this if the existing warnings: Vec<...> path doesn't already fit cleanly.
jiji-network::service_runtime
Replace the boolean-shaped shared_with_container: Option<String> field with a small enum so host isn't bolted on as a third parallel Option:
New NetworkedContainerRun::host(engine, container_name, image, server): no address/dns_address role to play in --ip/--dns rendering the way Bridge does, but does still render --dns/--dns-search {DEFAULT_SERVICE_DOMAIN}/--dns-option ndots:1 pointed at server.dns_address -- unlike SharedContainer, a host-mode container does not inherit anything else's resolver config, and it still needs .jiji resolution to reach sibling services. Confirmed working on both engines locally (see "Current state" above); no further verification needed on this specific point, though it should still be exercised in the live-host pass below against the project's actual DNS agent, not just a public resolver.
args() becomes a three-arm match: Bridge renders today's --network {bridge_name} --ip ... --dns ...; SharedContainer renders today's --network container:<target> with no addressing flags at all; Host renders --network host --dns {dns_address} --dns-search ... --dns-option ndots:1, and never-p (per the Podman gotcha above -- render_ports must not be called on this path regardless of what's in service.ports, since the plan already restricts it to zero rendered flags).
Update the two existing service_runtime.rs unit tests (dynamic_run_pins_address_dns_and_project_bridge, shared_run_joins_the_target_container_and_omits_its_own_addressing) for the enum rename, and add host_run_uses_network_host_and_keeps_dns.
jiji-cli::container_runtime
New build_host_run(...), structurally parallel to build_shared_run: same labels/restart/mounts/env-file/resource-options plumbing as build_dynamic_run, but calls NetworkedContainerRun::host(...) and never calls render_ports.
render_dynamic_labels still takes an address for the jiji.lease= label; pass server.management_address there for observability, the same role upstream_address already plays for the service:<name> case (labeled, never rendered into --ip).
jiji-cli::deploy_transaction
deploy_endpoint currently matches on ctx.service.network_mode_dependency() (Some -> deploy_shared_endpoint, None -> deploy_dynamic_endpoint). Extend to a three-way check: network_mode == "host" -> new deploy_host_mode, dependency Some -> deploy_shared_endpoint (unchanged), else -> deploy_dynamic_endpoint (unchanged).
deploy_host_mode mirrors deploy_shared_endpoint's shape (see its doc comment at deploy_transaction.rs:54) but is closer to the dynamic path than the shared-endpoint path: it still creates a genuinely new container and still needs candidate/active/draining/tombstone catalog transitions and the container-readiness health check, it just skips the AllocateAddress RPC and uses ctx.server.management_address as address everywhere the dynamic path currently uses the leased address (catalog commit, health check target if ever needed, jiji.lease label). No RequestBody::AllocateAddress call, and per the remove.rs/deploy_shared_endpoint precedent above, the existing unconditional ReleaseAddress call on removal needs no change -- it already no-ops correctly when nothing was ever leased for this deployment_id.
commit_catalog's ports argument: pass the single declared port (if any) converted to the same Vec<u16> shape targets/tcp_targets already produce for the bridge path, so DNS/catalog consumers see a consistent shape; proxy: being forbidden means this is metadata only for host mode today, not a routing input.
jiji-cli::container_ops
create_and_start's Podman bridge-DNS-reconcile skip (run.shared_with_container.is_some()) becomes matches!(run.network_target, NetworkTarget::SharedContainer(_) | NetworkTarget::Host) after the enum rename above.
Validation (crates/jiji-config/src/validation.rs)
Add validate_host_network_ports(config, errors): for every service with network_mode == "host", enforce at most one ports: entry, reject a host:container mapping or /udp suffix in that entry (bare port only), and run the per-(server, port) uniqueness check across every host-mode service pair whose servers: sets intersect.
Add rejection of network_mode == "none": a clear ValidationError (new code, e.g. NETWORK_MODE_NONE_UNSUPPORTED) telling the user network isolation without any reachable address isn't supported, and pointing at service:<name> (explicit sharing) or crons: (isolated one-off/scheduled work) as the supported alternatives.
Call both from the existing per-service validation loop in validate_config, next to validate_service_crons/ validate_service_build_secrets.
Testing plan
Same layered approach the build-secrets feature used (docs/architecture-notes.md#testing-boundaries):
Pure unit tests: service_runtime.rs (new host() constructor and args() arm), container_runtime.rs (build_host_run renders no -p, renders --network host and DNS flags, renders labels with management_address), validation.rs (host port count/shape rules, per-server port collision, none rejection, and that host still correctly triggers the existing NON_BRIDGE_SCALE/NON_BRIDGE_PROXY checks unchanged).
Mock-SSH integration tests (crates/jiji-cli/tests/deploy_test.rs pattern, TestServer/CannedResponse): a host-mode deploy renders a --network host run command with no -p, never calls the agent's AllocateAddress RPC (assert on the canned command/API log), commits a catalog record whose address is the server's management_address, and a service remove afterward still issues its normal (no-op) release call without erroring.
Live-host verification (required before calling this done, per docs/architecture-notes.md#testing-boundaries): deploy a real host-mode service on a real VPS on both Docker and Podman, confirm the process is reachable at the host's address on the declared port, confirm .jiji resolution of another service's DNS name works from inside a host-networked container against the project's real DNS agent on both engines (local testing already confirmed --dns/--network host compose correctly in general; this pass checks it against jiji's actual setup, not just a public resolver), and confirm jiji service remove and jiji server teardown both clean it up correctly. Use a test VPS and tear down fully afterward.
Documentation updates (same change as the implementation, not deferred)
crates/jiji-config/src/jiji.yml: replace the current "accepted by the schema but not implemented" note for network_mode with real documentation of host (scope, the single-port rule, the per-server uniqueness limit and its project-scoped blind spot, the isolation trade-off) and remove none from the list of accepted values, or document it as explicitly rejected with the reasoning above.
Website app/docs/reference/configuration/page.mdx (~/Code/jiji-website): same content, in the existing network_mode section.
AGENTS.md: add a short paragraph under "Container Namespace Sharing" or its own small section documenting network_mode: host's scope and the none rejection, matching the level of detail that section already gives service:<name>.
docs/todo.md: remove the "Implement or reject network_mode: host and network_mode: none" section once this plan ships, and update the "Keep public documentation within implemented behavior" bullet that currently points at it.
Task checklist
jiji-network::service_runtime: NetworkTarget enum, host() constructor, three-arm args(), updated/new unit tests.
jiji-cli::container_runtime: build_host_run, no -p rendering.
jiji-cli::deploy_transaction: deploy_host_mode branch, skip AllocateAddress RPC, use management_address.
jiji-cli::container_ops: skip Podman bridge-DNS reconcile for Host (and keep the existing skip for SharedContainer).
jiji-config::validation: host port-count/shape rule, per-server port collision rule, none rejection, unit tests.
Mock-SSH integration test(s) for a full host-mode deploy + remove.
Live-host verification on Docker and Podman (real VPS), including .jiji DNS resolution against the project's real DNS agent.
Close the gap described in
docs/todo.md:network_mode: hostandnetwork_mode: noneare accepted by the config schema and documented incrates/jiji-config/src/jiji.yml, but no runtime code path reads either value (container_runtime::build_dynamic_runalways renders normal project bridge networking). A service configured with either value silently gets bridge networking instead, which is a correctness gap: the config the user wrote does not describe the container that actually runs.Decision: implement
hostwith a small, well-defined scope; rejectnoneduring validation with an actionable error. Rationale below.Current state (confirmed by reading the code, not the docs)
Service.network_mode: String(crates/jiji-config/src/schema.rs), any string is accepted except acontainer:prefix, which validation already rejects (UNSUPPORTED_NETWORK_MODE) in favor ofservice:<name>.bridgemodes generically:replicas > 1requiresbridge(NON_BRIDGE_SCALE), andproxy:requiresbridge(NON_BRIDGE_PROXY). These two rules already apply tohost/nonetoday and need no change.Service.network_mode_dependency()only matches aservice:prefix, sohost/nonenever take that branch anywhere.healthcheck:can be attached to is aproxytarget (ProxyTarget/TCP targethealthcheck: Option<HealthcheckConfig>inschema.rs). Sinceproxy:is already forbidden for non-bridge modes,host/nonecan never carry an HTTP/command healthcheck today --deploy_transaction.rsalways falls back to the engine-native container-readiness check ({engine} inspect ... State.Status == running) for them, by construction. This removes most of the apparent complexity: there is no HTTP health-check path to design for either mode in this round.crates/jiji-network/src/service_runtime.rs::NetworkedContainerRunhas exactly two shapes today:dynamic(bridge, leased--ip,--dns*) andshared(--network container:<target>, no addressing flags at all, used fornetwork_mode: service:<name>).args()matches onshared_with_container: Option<String>.deploy_transaction.rs::deploy_endpointalready branches onctx.service.network_mode_dependency()to choosedeploy_shared_endpoint(no address lease, reuses the upstream's address) vs.deploy_dynamic_endpoint(leases a fresh address per deployment via the agent'sAllocateAddressRPC).hostneeds a third branch of the same shape.CatalogRecord.address: Ipv4Addris non-optional and is whatjiji-agent/src/dns.rspublishes as the.jijiDNS answer. Making it optional would be a distributed catalog schema change (replicated, version-checked between agents perdocs/architecture-notes.md#distributed-control-plane) -- far more invasive than this gap justifies, andnonemode has no address to put there under any design.ServerPlan.management_address(crates/jiji-network/src/planner.rs) already exists: the server's own WireGuard mesh address, computed deterministically, no lease required. This is the naturaladdressfor ahost-mode catalog record -- the container shares the host's entire network namespace, including the WireGuard interface, so anything the container binds becomes reachable atmanagement_address:<port>from the rest of the mesh, and at the host's own address from outside it.commands/service/remove.rs'sReleaseAddresscall is already unconditional (no "is this a dependent" check) and is already exercised today forservice:<name>dependents, which never held a lease. The agent's release handler already tolerates "nothing to release." No change needed there forhost.-pcombined with--network hostis a discarded-with-warning on both engines, not a hard error (docker: "WARNING: Published ports are discarded when using host network mode", exit 0;podman: "Port mappings have been discarded because "host" network namespace mode does not support them", exit 0). Older Podman releases have been reported to hard-error on this combination instead of warning, so treat "never render-pfor ahost-mode container" as a hard requirement regardless -- relying on either engine's discard-and-warn behavior would still spam noisy warnings into every deploy's output and is not something to depend on across engine versions.--dns 9.9.9.9 --dns-search example.internal --dns-option ndots:1combined with--network hostworks correctly on both engines -- each writes the requested/etc/resolv.confinto the container despite it sharing the host's network namespace (verified viacat /etc/resolv.confinside the container)..jijiresolution for ahost-mode container is safe to design around; no open question here.container_ops::create_and_startcurrently reconciles the project bridge's Podman DNS address only whenrun.shared_with_container.is_none(). Ahost-mode container has no bridge attachment either and needs the same skip.Why reject
noneinstead of implementing itnetwork_mode: nonegives a container no network at all. Every invariant this project relies on assumes a service has some reachable address:active+healthyrecord with a real address (docs/architecture-notes.md, "Non-negotiable invariants").addressfield is not optional, and making it optional is a replicated-schema change, not acontainer_runtime.rschange.nonemode is actually good for -- an isolated one-off or scheduled task that talks to nothing -- is already served bycrons:, which run in their own independently-addressed container regardless of the owning service'snetwork_mode(cron_exec.rs).There is no address to publish, no way to health-check beyond "process exists" (already true for
hosttoo, so that alone isn't disqualifying), and no realistic caller need thatservice:<name>(explicit sharing) or plainbridge(default isolation) doesn't already cover. Implementing it would mean carrying a permanently-address-less code path through the catalog, DNS, and reconciliation layers for a case with no real use. Rejecting it during validation is a two-line change and leaves the door open to revisit if a real need shows up.Design:
network_mode: hostConfig surface
ports:becomes at most one entry, a bare container-side port number only ("8080"), never ahost:containermapping and never a/udpsuffix (both are meaningless once the container shares the host's ports directly -- the app must already bind the port it wants). This entry is never rendered as-p; it exists purely so jiji knows what the service is listening on, for the per-server uniqueness check below and for any future host-mode healthcheck-by-port work.proxy:andreplicas > 1stay rejected by the existing generic non-bridge checks (NON_BRIDGE_PROXY,NON_BRIDGE_SCALE); no change.validate_tcp_targets's per-projectseen_listen_portspattern but keyed by(server, port)instead of justport: twohost-mode services whoseservers:sets overlap must not declare the same port. Actionable error naming both services and the shared server.host-mode services on a shared machine, so a cross-project port collision is only caught at container start (bind: address already in use), surfacing as a stuck "starting container" / failed health check the same way any other host-resource conflict does today. This is the same limitation the raw TCP proxy section already discloses for its own port space (docs/architecture-notes.md#shared-ingress-proxy).host-mode service still allowsvolumes:/files:/directories:,devices:,privileged:,cap_add:,cpus:/memory:/gpus:, andcrons:-- none of those interact with networking, so none of the existing rules for them need to change.network_mode: hostis combined withenvironment.secrets/typical "protect this" config, or simply document the caveat instead of a warning: host networking removes jiji's mesh isolation for that container -- it can reach (and be reached by) anything the host's own network can, bypassing the project bridge entirely. Land this as documentation; do not invent a new warning mechanism just for this if the existingwarnings: Vec<...>path doesn't already fit cleanly.jiji-network::service_runtimeReplace the boolean-shaped
shared_with_container: Option<String>field with a small enum sohostisn't bolted on as a third parallelOption:NetworkedContainerRun::dynamic(...)keeps constructingBridge.NetworkedContainerRun::shared(...)keeps constructingSharedContainer(...).NetworkedContainerRun::host(engine, container_name, image, server): noaddress/dns_addressrole to play in--ip/--dnsrendering the wayBridgedoes, but does still render--dns/--dns-search {DEFAULT_SERVICE_DOMAIN}/--dns-option ndots:1pointed atserver.dns_address-- unlikeSharedContainer, a host-mode container does not inherit anything else's resolver config, and it still needs.jijiresolution to reach sibling services. Confirmed working on both engines locally (see "Current state" above); no further verification needed on this specific point, though it should still be exercised in the live-host pass below against the project's actual DNS agent, not just a public resolver.args()becomes a three-arm match:Bridgerenders today's--network {bridge_name} --ip ... --dns ...;SharedContainerrenders today's--network container:<target>with no addressing flags at all;Hostrenders--network host --dns {dns_address} --dns-search ... --dns-option ndots:1, and never-p(per the Podman gotcha above --render_portsmust not be called on this path regardless of what's inservice.ports, since the plan already restricts it to zero rendered flags).service_runtime.rsunit tests (dynamic_run_pins_address_dns_and_project_bridge,shared_run_joins_the_target_container_and_omits_its_own_addressing) for the enum rename, and addhost_run_uses_network_host_and_keeps_dns.jiji-cli::container_runtimebuild_host_run(...), structurally parallel tobuild_shared_run: same labels/restart/mounts/env-file/resource-options plumbing asbuild_dynamic_run, but callsNetworkedContainerRun::host(...)and never callsrender_ports.render_dynamic_labelsstill takes an address for thejiji.lease=label; passserver.management_addressthere for observability, the same roleupstream_addressalready plays for theservice:<name>case (labeled, never rendered into--ip).jiji-cli::deploy_transactiondeploy_endpointcurrently matches onctx.service.network_mode_dependency()(Some->deploy_shared_endpoint,None->deploy_dynamic_endpoint). Extend to a three-way check:network_mode == "host"-> newdeploy_host_mode, dependencySome->deploy_shared_endpoint(unchanged), else ->deploy_dynamic_endpoint(unchanged).deploy_host_modemirrorsdeploy_shared_endpoint's shape (see its doc comment atdeploy_transaction.rs:54) but is closer to the dynamic path than the shared-endpoint path: it still creates a genuinely new container and still needs candidate/active/draining/tombstone catalog transitions and the container-readiness health check, it just skips theAllocateAddressRPC and usesctx.server.management_addressasaddresseverywhere the dynamic path currently uses the leased address (catalog commit, health check target if ever needed,jiji.leaselabel). NoRequestBody::AllocateAddresscall, and per theremove.rs/deploy_shared_endpointprecedent above, the existing unconditionalReleaseAddresscall on removal needs no change -- it already no-ops correctly when nothing was ever leased for thisdeployment_id.commit_catalog'sportsargument: pass the single declared port (if any) converted to the sameVec<u16>shapetargets/tcp_targetsalready produce for the bridge path, so DNS/catalog consumers see a consistent shape;proxy:being forbidden means this is metadata only forhostmode today, not a routing input.jiji-cli::container_opscreate_and_start's Podman bridge-DNS-reconcile skip (run.shared_with_container.is_some()) becomesmatches!(run.network_target, NetworkTarget::SharedContainer(_) | NetworkTarget::Host)after the enum rename above.Validation (
crates/jiji-config/src/validation.rs)validate_host_network_ports(config, errors): for every service withnetwork_mode == "host", enforce at most oneports:entry, reject ahost:containermapping or/udpsuffix in that entry (bare port only), and run the per-(server, port)uniqueness check across everyhost-mode service pair whoseservers:sets intersect.network_mode == "none": a clearValidationError(new code, e.g.NETWORK_MODE_NONE_UNSUPPORTED) telling the user network isolation without any reachable address isn't supported, and pointing atservice:<name>(explicit sharing) orcrons:(isolated one-off/scheduled work) as the supported alternatives.validate_config, next tovalidate_service_crons/validate_service_build_secrets.Testing plan
Same layered approach the build-secrets feature used (
docs/architecture-notes.md#testing-boundaries):service_runtime.rs(newhost()constructor andargs()arm),container_runtime.rs(build_host_runrenders no-p, renders--network hostand DNS flags, renders labels withmanagement_address),validation.rs(host port count/shape rules, per-server port collision,nonerejection, and thathoststill correctly triggers the existingNON_BRIDGE_SCALE/NON_BRIDGE_PROXYchecks unchanged).crates/jiji-cli/tests/deploy_test.rspattern,TestServer/CannedResponse): ahost-mode deploy renders a--network hostrun command with no-p, never calls the agent'sAllocateAddressRPC (assert on the canned command/API log), commits a catalog record whose address is the server'smanagement_address, and aservice removeafterward still issues its normal (no-op) release call without erroring.docs/architecture-notes.md#testing-boundaries): deploy a realhost-mode service on a real VPS on both Docker and Podman, confirm the process is reachable at the host's address on the declared port, confirm.jijiresolution of another service's DNS name works from inside a host-networked container against the project's real DNS agent on both engines (local testing already confirmed--dns/--network hostcompose correctly in general; this pass checks it against jiji's actual setup, not just a public resolver), and confirmjiji service removeandjiji server teardownboth clean it up correctly. Use a test VPS and tear down fully afterward.Documentation updates (same change as the implementation, not deferred)
crates/jiji-config/src/jiji.yml: replace the current "accepted by the schema but not implemented" note fornetwork_modewith real documentation ofhost(scope, the single-port rule, the per-server uniqueness limit and its project-scoped blind spot, the isolation trade-off) and removenonefrom the list of accepted values, or document it as explicitly rejected with the reasoning above.app/docs/reference/configuration/page.mdx(~/Code/jiji-website): same content, in the existingnetwork_modesection.AGENTS.md: add a short paragraph under "Container Namespace Sharing" or its own small section documentingnetwork_mode: host's scope and thenonerejection, matching the level of detail that section already givesservice:<name>.docs/todo.md: remove the "Implement or rejectnetwork_mode: hostandnetwork_mode: none" section once this plan ships, and update the "Keep public documentation within implemented behavior" bullet that currently points at it.Task checklist
jiji-network::service_runtime:NetworkTargetenum,host()constructor, three-armargs(), updated/new unit tests.jiji-cli::container_runtime:build_host_run, no-prendering.jiji-cli::deploy_transaction:deploy_host_modebranch, skipAllocateAddressRPC, usemanagement_address.jiji-cli::container_ops: skip Podman bridge-DNS reconcile forHost(and keep the existing skip forSharedContainer).jiji-config::validation: host port-count/shape rule, per-server port collision rule,nonerejection, unit tests.host-mode deploy + remove..jijiDNS resolution against the project's real DNS agent.jiji.yml, website configuration reference,AGENTS.mdupdated.docs/todo.mdgap section removed.mise build/mise lint/mise test/mise scanall clean.