nomarmy 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
Files changed (102) hide show
  1. package/README.md +86 -480
  2. package/bin/nomarmy.mjs +1081 -185
  3. package/docker/Dockerfile +2 -2
  4. package/docker/Dockerfile.go +6 -4
  5. package/docker/Dockerfile.rust +17 -2
  6. package/harnesses/_template/README.md +27 -0
  7. package/harnesses/_template/harness.yml +26 -0
  8. package/harnesses/browser-playwright/README.md +35 -0
  9. package/harnesses/browser-playwright/fixture/package.json +1 -0
  10. package/harnesses/browser-playwright/fixture/page.html +1 -0
  11. package/harnesses/browser-playwright/fixture/page.spec.js +5 -0
  12. package/harnesses/browser-playwright/fixture/playwright.config.js +8 -0
  13. package/harnesses/browser-playwright/harness.yml +18 -0
  14. package/harnesses/go/README.md +45 -0
  15. package/harnesses/go/harness.yml +14 -0
  16. package/harnesses/mock-oidc/README.md +31 -0
  17. package/harnesses/mock-oidc/fixture/.nomarmy.yml +4 -0
  18. package/harnesses/mock-oidc/fixture/discovery.test.mjs +16 -0
  19. package/harnesses/mock-oidc/harness.yml +19 -0
  20. package/harnesses/node/README.md +53 -0
  21. package/harnesses/node/harness.yml +18 -0
  22. package/harnesses/python/README.md +46 -0
  23. package/harnesses/python/harness.yml +16 -0
  24. package/harnesses/rust/README.md +45 -0
  25. package/harnesses/rust/harness.yml +13 -0
  26. package/install.sh +29 -9
  27. package/lib/admission.mjs +178 -30
  28. package/lib/agents.mjs +8 -6
  29. package/lib/army.mjs +25 -10
  30. package/lib/codex-link.mjs +37 -0
  31. package/lib/config.mjs +15 -0
  32. package/lib/connect.mjs +232 -19
  33. package/lib/continue-from.mjs +103 -0
  34. package/lib/coordinator-instructions.mjs +5 -1
  35. package/lib/diff-checks.mjs +114 -0
  36. package/lib/dispatch-schema.mjs +14 -12
  37. package/lib/doctor.mjs +98 -9
  38. package/lib/egress-proxy.mjs +116 -0
  39. package/lib/execute.mjs +241 -33
  40. package/lib/git-record.mjs +27 -3
  41. package/lib/harness-schema.mjs +61 -0
  42. package/lib/harnesses.mjs +99 -0
  43. package/lib/health.mjs +162 -18
  44. package/lib/install-freshness.mjs +114 -0
  45. package/lib/jev-checks.mjs +110 -0
  46. package/lib/job-format.mjs +54 -0
  47. package/lib/judge.mjs +130 -0
  48. package/lib/limits.mjs +77 -0
  49. package/lib/model-probe.mjs +61 -0
  50. package/lib/mutation.mjs +159 -0
  51. package/lib/notify.mjs +30 -3
  52. package/lib/openclaw-install.mjs +122 -0
  53. package/lib/openclaw-path.mjs +28 -0
  54. package/lib/openclaw-run.mjs +74 -12
  55. package/lib/openclaw-runtime-health.mjs +56 -0
  56. package/lib/outcome.mjs +21 -2
  57. package/lib/outcomes.mjs +6 -0
  58. package/lib/path-utils.mjs +4 -0
  59. package/lib/podman-health.mjs +41 -0
  60. package/lib/process.mjs +4 -1
  61. package/lib/propose.mjs +10 -11
  62. package/lib/refusal-retry.mjs +16 -0
  63. package/lib/registry-python.mjs +98 -0
  64. package/lib/registry-secrets.mjs +140 -0
  65. package/lib/repo-query.mjs +13 -7
  66. package/lib/runs.mjs +7 -1
  67. package/lib/same-path.mjs +14 -0
  68. package/lib/sandbox-images.mjs +499 -83
  69. package/lib/sandbox-vm.mjs +32 -0
  70. package/lib/scan.mjs +5 -1
  71. package/lib/schema.mjs +20 -11
  72. package/lib/scout.mjs +21 -3
  73. package/lib/server-context.mjs +21 -1
  74. package/lib/setup-steps.mjs +55 -0
  75. package/lib/share.mjs +82 -0
  76. package/lib/stale-sessions.mjs +60 -0
  77. package/lib/stats.mjs +315 -0
  78. package/lib/statusline.mjs +32 -6
  79. package/lib/subscription-setup.mjs +13 -0
  80. package/lib/suggestions.mjs +153 -0
  81. package/lib/thinking.mjs +23 -0
  82. package/lib/transcript.mjs +30 -5
  83. package/lib/usage-limits.mjs +329 -0
  84. package/lib/user-config.mjs +106 -0
  85. package/lib/validators.mjs +220 -0
  86. package/lib/verification-artifacts.mjs +46 -0
  87. package/lib/verification-flow.mjs +52 -7
  88. package/lib/verification-network.mjs +66 -0
  89. package/lib/verify.mjs +338 -85
  90. package/lib/worker-prompt.mjs +5 -2
  91. package/lib/wsl-cli.mjs +152 -0
  92. package/lib/wsl.mjs +230 -0
  93. package/lib/zod-issues.mjs +15 -0
  94. package/mcp/server.mjs +165 -34
  95. package/package.json +7 -5
  96. package/playbooks/feature.md +8 -5
  97. package/scripts/configure-openclaw.sh +4 -2
  98. package/scripts/generate-harness-docs.mjs +42 -0
  99. package/scripts/install-openclaw.mjs +23 -0
  100. package/scripts/lib.sh +9 -2
  101. package/scripts/select-model.mjs +12 -5
  102. package/scripts/start-inference.sh +2 -2
package/docker/Dockerfile CHANGED
@@ -1,9 +1,9 @@
1
- FROM node:24-bookworm-slim
1
+ FROM node:26-bookworm-slim
2
2
  ENV DEBIAN_FRONTEND=noninteractive
3
3
  RUN apt-get update && apt-get install -y --no-install-recommends \
4
4
  bash ca-certificates curl git jq python3 python3-pip python3-venv ripgrep \
5
5
  && rm -rf /var/lib/apt/lists/*
6
- # node:24-bookworm-slim already ships a non-root "node" user at uid/gid 1000,
6
+ # The node:*-bookworm-slim images ship a non-root "node" user at uid/gid 1000,
7
7
  # the same uid this Dockerfile used to try to create a "sandbox" user at.
8
8
  # useradd silently failed on every build (masked by `|| true`), so `USER
9
9
  # sandbox` below referred to a user that was never actually created --
@@ -1,4 +1,4 @@
1
- FROM node:24-bookworm-slim
1
+ FROM node:26-bookworm-slim
2
2
  ENV DEBIAN_FRONTEND=noninteractive
3
3
  # Same base as docker/Dockerfile plus a Go toolchain -- the worker's own
4
4
  # tool-calling harness needs Node regardless of the target repo's language.
@@ -9,15 +9,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
9
9
  # (verified live: apt installed 1.19 here while go.dev's stable was 1.27) --
10
10
  # same reasoning as using rustup instead of apt for Rust in Dockerfile.rust.
11
11
  # Pinned, not "latest", for a reproducible build; bump GO_VERSION by hand
12
- # periodically against https://go.dev/dl/.
12
+ # periodically against https://go.dev/dl/, with both SHA256 values from
13
+ # https://go.dev/dl/?mode=json.
13
14
  ENV GO_VERSION=1.27.1
14
15
  RUN ARCH="$(dpkg --print-architecture)" \
15
16
  && case "$ARCH" in \
16
- amd64) GOARCH=amd64 ;; \
17
- arm64) GOARCH=arm64 ;; \
17
+ amd64) GOARCH=amd64; SHA256=63d339f0da5ab53635a56f2490a7984dfe12dfcff22ad749f63edaf590168445 ;; \
18
+ arm64) GOARCH=arm64; SHA256=3450b45a3f9ee8568792736a5c5e70a1f2e9b36c35a8f74958c03e51d7d92bec ;; \
18
19
  *) echo "unsupported architecture for Go install: $ARCH" >&2; exit 1 ;; \
19
20
  esac \
20
21
  && curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz" -o /tmp/go.tgz \
22
+ && echo "${SHA256} /tmp/go.tgz" | sha256sum -c - \
21
23
  && tar -C /usr/local -xzf /tmp/go.tgz \
22
24
  && rm /tmp/go.tgz
23
25
  ENV PATH="/usr/local/go/bin:${PATH}"
@@ -1,4 +1,4 @@
1
- FROM node:24-bookworm-slim
1
+ FROM node:26-bookworm-slim
2
2
  ENV DEBIAN_FRONTEND=noninteractive
3
3
  # Same base as docker/Dockerfile plus a Rust toolchain -- the worker's own
4
4
  # tool-calling harness needs Node regardless of the target repo's language.
@@ -14,6 +14,21 @@ USER node
14
14
  ENV RUSTUP_HOME=/home/node/.rustup
15
15
  ENV CARGO_HOME=/home/node/.cargo
16
16
  ENV PATH="${CARGO_HOME}/bin:${PATH}"
17
- RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
17
+ # rustup-init is pinned by version and checksum instead of piping
18
+ # sh.rustup.rs into sh; rustup then verifies the toolchain it downloads.
19
+ # Bump RUSTUP_VERSION and both hashes together, from
20
+ # https://static.rust-lang.org/rustup/archive/<version>/<target>/rustup-init.sha256.
21
+ ENV RUSTUP_VERSION=1.29.1
22
+ RUN ARCH="$(dpkg --print-architecture)" \
23
+ && case "$ARCH" in \
24
+ amd64) TARGET=x86_64-unknown-linux-gnu; SHA256=dda7234360b7f578ca8b0ddcb80145646fa61a67c1720a5abc7051b35c9fcb71 ;; \
25
+ arm64) TARGET=aarch64-unknown-linux-gnu; SHA256=15f6e4ce9f583b929c996c91562bad6d4454f3281de858b02cdfdef615fac433 ;; \
26
+ *) echo "unsupported architecture for Rust install: $ARCH" >&2; exit 1 ;; \
27
+ esac \
28
+ && curl --proto '=https' --tlsv1.2 -fsSL "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${TARGET}/rustup-init" -o /tmp/rustup-init \
29
+ && echo "${SHA256} /tmp/rustup-init" | sha256sum -c - \
30
+ && chmod +x /tmp/rustup-init \
31
+ && /tmp/rustup-init -y --no-modify-path --profile minimal --default-toolchain stable \
32
+ && rm /tmp/rustup-init
18
33
  WORKDIR /workspace
19
34
  CMD ["sleep", "infinity"]
@@ -0,0 +1,27 @@
1
+ # Harness name
2
+
3
+ Write a short summary of what this harness provides.
4
+
5
+ ## Detection
6
+
7
+ List the markers and packages that match. Explain any `after` layers.
8
+
9
+ ## Installed today
10
+
11
+ List tools and dependencies, versions, build commands, and known gaps.
12
+
13
+ ## Verification and network
14
+
15
+ Document each proposed profile. Declare the lowest network level needed.
16
+ For allowlist verification, document operator opt-in and dedicated test tenants
17
+ with throwaway credentials, never production. Workers always remain offline.
18
+ This registry does not enable services or allowlist networking yet.
19
+
20
+ ## Requirements and artifacts
21
+
22
+ Explain memory, shared memory, KVM, and artifact globs, if any.
23
+
24
+ ## Fixture
25
+
26
+ Add a tiny repository under `fixture/`. Describe how to verify it and its
27
+ expected results. Keep credentials and host-specific paths out of fixtures.
@@ -0,0 +1,26 @@
1
+ # Copy this folder to harnesses/<name>. Underscore folders are ignored.
2
+ # name: example-harness # Required kebab-case name, equal to the folder name.
3
+ # summary: Short description of what this installs.
4
+ # detect: # Any matching rule applies. Paths are relative to the repo.
5
+ # - file: example.config.json
6
+ # - package: example-package # A Node dependency in a discovered package.json.
7
+ # - lockfile: example.lock
8
+ # after: [node] # Existing harnesses that must precede this one.
9
+ # image: # Choose declarative apt/run OR builtin, never both.
10
+ # apt: [example-package] # OS packages installed at image build time.
11
+ # run: ["example-install --frozen"] # Build commands, not worker commands.
12
+ # # builtin: node # Install logic implemented in lib/.
13
+ # verification: # Proposed profiles mapped to commands or command lists.
14
+ # quick: example-test
15
+ # full: [example-lint, example-test]
16
+ # artifacts: [test-results/**] # Repository-relative artifact globs.
17
+ # requires: # Optional resource requirements.
18
+ # memoryMb: 1024
19
+ # shmMb: 512
20
+ # kvm: false
21
+ # network: none # Default; services and allowlist are future verification levels.
22
+ # services: [] # Service names; omit entirely for network none, even if empty.
23
+ # suggestedRole: # Optional role suggestion.
24
+ # name: example-qa
25
+ # description: Runs the example suite.
26
+ # docs: README.md # Default; path relative to this harness folder.
@@ -0,0 +1,35 @@
1
+ # Playwright browser harness
2
+
3
+ Installs Chromium and its OS dependencies using the repository's installed
4
+ @playwright/test version, after the Node dependency layer. Commit a supported
5
+ Node lockfile. Root and discovered nested packages are supported; browsers live
6
+ in /ms-playwright and are readable by the node user. Installation failure leaves
7
+ a .nomarmy-playwright-install-failed marker in /ms-playwright.
8
+
9
+ Select the browser verification profile (npx playwright test) in .nomarmy.yml:
10
+
11
+ ```yaml
12
+ verification:
13
+ browser:
14
+ commands: [npx playwright test]
15
+ environment: none
16
+ ```
17
+
18
+ Allow at least 1024 MB memory. nomArmy's verification container allocates 512 MB
19
+ /dev/shm (or the largest shared-memory requirement of the matched harnesses).
20
+ For OpenClaw-managed worker sandboxes, unless OpenClaw exposes a shm setting,
21
+ launch Chromium with --disable-dev-shm-usage via Playwright launchOptions, as
22
+ the fixture does.
23
+
24
+ The fixture opens its own static HTML with file://: the test needs no network.
25
+ Install its npm dependencies and Chromium before running offline.
26
+
27
+ Screenshots, traces and reports under test-results/** and playwright-report/**
28
+ become job evidence under artifacts/, listed in verification.artifacts.
29
+ Collection is limited to 200 files and 50 MB; a cap is reported in verification.
30
+ Most of the time the General checks the UI itself after merging. This harness
31
+ is for repositories with a real end-to-end suite as their gate.
32
+
33
+ ## Running the fixture
34
+
35
+ `fixture/` has no lockfile on purpose: with one, nomArmy's own repository would match this harness and every nomArmy job would build Chromium into its image. To try it, copy the folder somewhere, run `npm install --package-lock-only` there (with network), commit, and verify it with the `browser` profile. Checked this way: headless Chromium passes offline, and the screenshot and trace land in the job's `artifacts/`.
@@ -0,0 +1 @@
1
+ {"private":true,"type":"module","scripts":{"test":"playwright test"},"devDependencies":{"@playwright/test":"1.55.1"}}
@@ -0,0 +1 @@
1
+ <!doctype html><title>Offline fixture</title><h1>Browser ready</h1>
@@ -0,0 +1,5 @@
1
+ import { test, expect } from '@playwright/test';
2
+ test('opens local fixture', async ({ page }) => {
3
+ await page.goto(new URL('./page.html', import.meta.url).href);
4
+ await expect(page.getByRole('heading')).toHaveText('Browser ready');
5
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from '@playwright/test';
2
+ export default defineConfig({
3
+ testDir: '.',
4
+ testMatch: 'page.spec.js',
5
+ reporter: [['html', { open: 'never' }]],
6
+ use: { browserName: 'chromium', screenshot: 'on', trace: 'on',
7
+ launchOptions: { args: ['--disable-dev-shm-usage'] } },
8
+ });
@@ -0,0 +1,18 @@
1
+ name: browser-playwright
2
+ summary: Offline Chromium end-to-end tests with the repository's Playwright version
3
+ detect:
4
+ - package: '@playwright/test'
5
+ - file: playwright.config.ts
6
+ - file: playwright.config.js
7
+ - file: playwright.config.mjs
8
+ after: [node]
9
+ image:
10
+ builtin: playwright
11
+ verification:
12
+ browser: npx playwright test
13
+ artifacts: [test-results/**, playwright-report/**]
14
+ requires:
15
+ memoryMb: 1024
16
+ shmMb: 512
17
+ network: none
18
+ docs: README.md
@@ -0,0 +1,45 @@
1
+ # Go harness
2
+
3
+ Go toolchain with prefetched modules for offline verification.
4
+
5
+ ## Detection
6
+
7
+ Matches `go.mod` or `go.work` at the repository root.
8
+
9
+ ## Installed today
10
+
11
+ Installs Go 1.27.1 from the upstream tarball for amd64 or arm64, on the Node 24 Debian bookworm base with Python 3 and pip. Sets GOPATH for the node user.
12
+
13
+ At image build, copies root `go.mod` and `go.sum` (when present), plus `go.work`, `go.work.sum` and each repository-contained workspace module’s manifests. Runs `go mod download` as node into the default module cache, `/home/node/go/pkg/mod`. Repositories with `vendor/` skip this download. A failed download leaves `/deps/go/.nomarmy-go-mod-download-failed` without failing the image build. Runtime sets `GOPROXY=off` and `GOSUMDB=off`; no `GOFLAGS` override is set, preserving Go’s default module behavior. Copied metadata is hashed into the image tag, so dependency changes rebuild the cache. The layer composes with Node dependencies in mixed repositories.
14
+
15
+ ## Verification and network
16
+
17
+ Proposes `go test ./...` as the `quick` profile. Network level: `none`. Matched harnesses compose the sandbox image used by workers and verification. The verification profile is a proposal; the registry does not change job profiles or network access.
18
+
19
+ ## Remaining limitations
20
+
21
+ Private modules and registries use the build-only credentials described below; no credentials are copied into the image. A failure marker means the cache may be incomplete and offline verification can still fail. Workspace modules and local replacements outside the repository are not copied.
22
+
23
+ ## Requirements and artifacts
24
+
25
+ No additional resource requirements or artifact globs are declared.
26
+
27
+
28
+ ## Private registries
29
+
30
+ Use `registries:` in the gitignored `.nomarmy.local.yml` only; the committed
31
+ config rejects it. Values are host credential **file paths**, never tokens.
32
+ Use `go: { netrc: ~/.netrc, private: "github.com/acme/*" }`. Only `go mod download` receives the node-owned netrc plus RUN-local GOPRIVATE and GONOSUMDB; vendored builds need no download mount.
33
+
34
+ Credentials stay on the host and enter Podman only as build-secret mounts;
35
+ no credential files are copied into the context or passed to sandbox jobs.
36
+ Use a **read-only, least-privilege token** and reviewed dependencies. Secrets
37
+ are accessible to install code during that RUN, so malicious packages are not
38
+ made safe by a mount. Credential rotation invalidates both tag and install
39
+ cache. Credentialed install output/build-error details are withheld.
40
+
41
+ See [Private registries](../../docs/your-repo.md#private-registries) for the
42
+ configuration and required manual canary check: inspect `podman history
43
+ --no-trunc`, image configuration, the exported filesystem **and every saved
44
+ image layer**, plus failure logs/job records, for absent secret bytes. Unit
45
+ tests stub Podman; live isolation needs independent security review.
@@ -0,0 +1,14 @@
1
+ name: go
2
+ summary: Go toolchain with prefetched modules for offline verification
3
+ detect:
4
+ - file: go.mod
5
+ - file: go.work
6
+ after: []
7
+ image:
8
+ builtin: go
9
+ verification:
10
+ quick: go test ./...
11
+ artifacts: []
12
+ requires: {}
13
+ network: none
14
+ docs: README.md
@@ -0,0 +1,31 @@
1
+ # Mock OIDC
2
+
3
+ Enable explicitly in your repository's `.nomarmy.yml` (there are no detection rules):
4
+
5
+ ```yaml
6
+ harnesses: [mock-oidc]
7
+ verification:
8
+ auth:
9
+ commands: [npm test]
10
+ ```
11
+
12
+ Point your application's issuer/discovery configuration at `OIDC_ISSUER` or
13
+ `OAUTH2_ISSUER`, both exported as `http://mock-oidc:8080/default` during nomArmy
14
+ verification. Configure your test client ID, redirect URI and audience to match
15
+ what your application expects; allow HTTP only in this test configuration.
16
+
17
+ The pinned NAV mock OAuth2 server supplies discovery, signing keys, token and
18
+ mock authorization flows. It is not an Okta/Auth0 tenant: it does not prove real
19
+ identity, MFA, tenant policies, upstream federation or production compatibility.
20
+ Never use real credentials with this fake.
21
+
22
+ nomArmy pulls the pinned image on the host if missing, then starts it without
23
+ published ports on a per-run `--internal` Podman network. Discovery must return
24
+ 2xx within 30 seconds before commands run. Containers and the network are
25
+ removed even if verification fails. Workers remain `--network none`; only
26
+ nomArmy's own verification can reach the fake. Env maps in harness manifests
27
+ are non-secret literals, not secret references or host-environment imports.
28
+
29
+ `fixture/` is a tiny Node-built-ins-only repository. Run its `quick` profile
30
+ through nomArmy verification. It fetches discovery over the private network;
31
+ it requires no internet or npm packages. It is not a host-side unit test.
@@ -0,0 +1,4 @@
1
+ harnesses: [mock-oidc]
2
+ verification:
3
+ quick:
4
+ commands: [node --test discovery.test.mjs]
@@ -0,0 +1,16 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+
4
+ test('private mock issuer discovery', async () => {
5
+ const issuer = process.env.OIDC_ISSUER;
6
+ assert.equal(issuer, 'http://mock-oidc:8080/default');
7
+ const response = await fetch(`${issuer}/.well-known/openid-configuration`, {
8
+ redirect: 'error', signal: AbortSignal.timeout(5000),
9
+ });
10
+ assert.equal(response.status, 200);
11
+ const document = await response.json();
12
+ assert.equal(document.issuer, issuer);
13
+ assert.equal(new URL(document.jwks_uri).origin, new URL(issuer).origin);
14
+ assert.equal(new URL(document.authorization_endpoint).origin, new URL(issuer).origin);
15
+ assert.equal(new URL(document.token_endpoint).origin, new URL(issuer).origin);
16
+ });
@@ -0,0 +1,19 @@
1
+ name: mock-oidc
2
+ summary: Mock OAuth2 and OIDC discovery and login on an isolated verification network
3
+ detect: []
4
+ image:
5
+ apt: []
6
+ run: []
7
+ network: services
8
+ services:
9
+ - name: mock-oidc
10
+ image: ghcr.io/navikt/mock-oauth2-server:2.1.10
11
+ port: 8080
12
+ env:
13
+ SERVER_PORT: '8080'
14
+ health: /default/.well-known/openid-configuration
15
+ env:
16
+ OIDC_ISSUER: http://mock-oidc:8080/default
17
+ OAUTH2_ISSUER: http://mock-oidc:8080/default
18
+ verification:
19
+ oidc: node discovery.test.mjs
@@ -0,0 +1,53 @@
1
+ # Node harness
2
+
3
+ Node dependencies and workspaces installed with npm, pnpm, yarn or bun.
4
+
5
+ ## Detection
6
+
7
+ Matches `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock` or `bun.lockb` at the root or in discovered package directories.
8
+
9
+ ## Installed today
10
+
11
+ Installs dependencies as the `node` user under `/deps` at image build time, on Node 24 / Debian bookworm:
12
+
13
+ - npm: `npm ci --no-audit --no-fund` (unchanged).
14
+ - pnpm: Corepack and `pnpm install --frozen-lockfile`.
15
+ - Yarn classic: Corepack and `yarn install --frozen-lockfile`.
16
+ - Yarn Berry: detected by `.yarnrc.yml` or lockfile `__metadata`; `YARN_NODE_LINKER=node-modules yarn install --immutable`.
17
+ - Bun: installed globally at build time, then `bun install --frozen-lockfile`.
18
+
19
+ Corepack respects `packageManager` in the manifest. Without a pin, Yarn defaults to classic or current stable Berry according to detection. Failed installs leave `.nomarmy-npm-ci-failed` for npm or `.nomarmy-<manager>-install-failed` for other managers (`yarn-berry` for Berry).
20
+
21
+ A manifest's `workspaces` array (or `workspaces.packages`) or `pnpm-workspace.yaml` causes one install at that workspace root. Member manifests are copied at their relative paths, including members without their own lockfile; workspace members are not installed twice. Workspace globs support `*`, `**`, `?`, brace alternatives and exclusions. Non-workspace packages retain separate installs.
22
+
23
+ Root dependencies resolve through `/node_modules`; workspace roots and members also receive worktree links into `/deps`, preserving pnpm's relative store links. The root binary PATH remains `/deps/node_modules/.bin`. Copied manifests, lockfiles and workspace/config files participate in the image tag.
24
+
25
+ Private registries use operator-local build secrets; see the configuration and Yarn Berry limitation below.
26
+
27
+ ## Verification and network
28
+
29
+ Proposes `npm test` as the `quick` profile. Network level: `none`. Matched harnesses compose the sandbox image used by workers and verification. The verification profile is a proposal; the registry does not change job profiles or network access.
30
+
31
+ ## Requirements and artifacts
32
+
33
+ No additional resource requirements or artifact globs are declared.
34
+
35
+
36
+ ## Private registries
37
+
38
+ Use `registries:` in the gitignored `.nomarmy.local.yml` only; the committed
39
+ config rejects it. Values are host credential **file paths**, never tokens.
40
+ npm, pnpm, Yarn Classic and bun read the mounted `.npmrc` as node (uid 1000). Credentialed Yarn Berry builds are currently rejected because Berry does not read `.npmrc`.
41
+
42
+ Credentials stay on the host and enter Podman only as build-secret mounts;
43
+ no credential files are copied into the context or passed to sandbox jobs.
44
+ Use a **read-only, least-privilege token** and reviewed dependencies. Secrets
45
+ are accessible to install code during that RUN, so malicious packages are not
46
+ made safe by a mount. Credential rotation invalidates both tag and install
47
+ cache. Credentialed install output/build-error details are withheld.
48
+
49
+ See [Private registries](../../docs/your-repo.md#private-registries) for the
50
+ configuration and required manual canary check: inspect `podman history
51
+ --no-trunc`, image configuration, the exported filesystem **and every saved
52
+ image layer**, plus failure logs/job records, for absent secret bytes. Unit
53
+ tests stub Podman; live isolation needs independent security review.
@@ -0,0 +1,18 @@
1
+ name: node
2
+ summary: Node dependencies and workspaces installed with npm, pnpm, yarn or bun
3
+ detect:
4
+ - lockfile: package-lock.json
5
+ - lockfile: npm-shrinkwrap.json
6
+ - lockfile: pnpm-lock.yaml
7
+ - lockfile: yarn.lock
8
+ - lockfile: bun.lock
9
+ - lockfile: bun.lockb
10
+ after: []
11
+ image:
12
+ builtin: node
13
+ verification:
14
+ quick: npm test
15
+ artifacts: []
16
+ requires: {}
17
+ network: none
18
+ docs: README.md
@@ -0,0 +1,46 @@
1
+ # Python harness
2
+
3
+ Python dependencies from uv, Poetry, pyproject, or requirements files.
4
+
5
+ ## Detection
6
+
7
+ Matches `uv.lock`, `poetry.lock`, `pyproject.toml`, or `requirements.txt` at the repository root. Explicit `environment.python.requirements` in `.nomarmy.yml` wins; otherwise the install precedence is uv.lock, poetry.lock (or `[tool.poetry]`), `[project]` in pyproject.toml, then requirements.txt.
8
+
9
+ ## Installed dependencies
10
+
11
+ Installs Python 3, pip, venv, and build-essential alongside the Node 24 base. Dependency downloads happen at image build time, not in the offline sandbox.
12
+
13
+ - **uv:** installs uv and runs `uv sync --frozen --no-install-project --all-groups`, including dev groups, from the copied pyproject.toml and uv.lock.
14
+ - **Poetry:** installs Poetry and runs `poetry install --no-root --no-interaction` from pyproject.toml and poetry.lock when present. Adds `--with dev` only when a Poetry dev group exists. Legacy dev dependencies follow Poetry's default behavior.
15
+ - **Plain pyproject:** uses Python's TOML parser to install `[project].dependencies` and optional `test`, `tests`, and `dev` dependencies with pip, without installing the project itself. A lockfile makes dependency resolution reproducible; unlocked pyproject dependencies may resolve differently on rebuild.
16
+ - **Requirements:** retains the existing system `pip3 install --no-cache-dir --break-system-packages` path. Uses requirements.txt by default; set `environment.python.requirements` to select other or multiple files. Explicit requirements also activate composition without a registry detect match.
17
+
18
+ The project-manager paths share `/deps/python/.venv`. `VIRTUAL_ENV` is set, and its bin directory comes first in both the image PATH and worker exec PATH. Install failures leave `/deps/python/.nomarmy-<manager>-install-failed` (uv, poetry, or pyproject) instead of failing the image build. Copied manifests and lockfiles are hashed into the image tag. Local/path dependencies and workspace members not included in the root metadata context may require an explicit requirements configuration.
19
+
20
+ ## Verification and network
21
+
22
+ The registry proposes `python3 -m unittest discover` as the `quick` profile. Config proposals use test commands found in evidence, or suggest `pytest` for project manifests; review that pytest is declared and appropriate. Network level: `none`. Matched harnesses compose the sandbox image used by workers and verification. Verification profiles are proposals; the registry does not change job profiles or network access.
23
+
24
+ ## Requirements and artifacts
25
+
26
+ No additional resource requirements or artifact globs are declared.
27
+
28
+
29
+ ## Private registries
30
+
31
+ Use `registries:` in the gitignored `.nomarmy.local.yml` only; the committed
32
+ config rejects it. Values are host credential **file paths**, never tokens.
33
+ pip uses `/etc/pip.conf`; for uv or Poetry use `pip: { netrc: ~/.netrc }` and credential-free index/source URLs in project metadata. The secret is mounted only for the root-owned install RUN, including venv installs.
34
+
35
+ Credentials stay on the host and enter Podman only as build-secret mounts;
36
+ no credential files are copied into the context or passed to sandbox jobs.
37
+ Use a **read-only, least-privilege token** and reviewed dependencies. Secrets
38
+ are accessible to install code during that RUN, so malicious packages are not
39
+ made safe by a mount. Credential rotation invalidates both tag and install
40
+ cache. Credentialed install output/build-error details are withheld.
41
+
42
+ See [Private registries](../../docs/your-repo.md#private-registries) for the
43
+ configuration and required manual canary check: inspect `podman history
44
+ --no-trunc`, image configuration, the exported filesystem **and every saved
45
+ image layer**, plus failure logs/job records, for absent secret bytes. Unit
46
+ tests stub Podman; live isolation needs independent security review.
@@ -0,0 +1,16 @@
1
+ name: python
2
+ summary: Python dependencies from uv, Poetry, pyproject, or requirements files
3
+ detect:
4
+ - file: uv.lock
5
+ - file: poetry.lock
6
+ - file: pyproject.toml
7
+ - file: requirements.txt
8
+ after: []
9
+ image:
10
+ builtin: python
11
+ verification:
12
+ quick: python3 -m unittest discover
13
+ artifacts: []
14
+ requires: {}
15
+ network: none
16
+ docs: README.md
@@ -0,0 +1,45 @@
1
+ # Rust harness
2
+
3
+ Rust toolchain with prefetched crates for offline verification.
4
+
5
+ ## Detection
6
+
7
+ Matches `Cargo.toml` at the repository root.
8
+
9
+ ## Installed today
10
+
11
+ Installs stable Rust through rustup with its minimal profile, plus build-essential, on the Node 24 Debian bookworm base with Python 3 and pip. Cargo and rustup live under the node user.
12
+
13
+ At image build, copies root and nested `Cargo.toml` files through four directory levels, excluding `target/` and dot folders, plus root `Cargo.lock` when present. The isolated build context includes placeholder library and binary sources and explicit target paths so Cargo can parse packages without copying application code. Runs `cargo fetch` as node (`--locked` when the lockfile exists), caching crates under `/home/node/.cargo`. A failed fetch leaves `/deps/rust/.nomarmy-cargo-fetch-failed` without failing the image build. Runtime sets `CARGO_NET_OFFLINE=true`. All copied manifests, the lockfile and generated targets contribute to the image tag. The layer composes with other matched harnesses.
14
+
15
+ ## Verification and network
16
+
17
+ Proposes `cargo test` as the `quick` profile. Network level: `none`. Matched harnesses compose the sandbox image used by workers and verification. The verification profile is a proposal; the registry does not change job profiles or network access.
18
+
19
+ ## Remaining limitations
20
+
21
+ Private modules and registries use the build-only credentials described below; no credentials are copied into the image. A failure marker means the cache may be incomplete and offline verification can still fail. Workspace members and path dependencies outside the repository or deeper than four directories are not copied. Root `.cargo/config` and `.cargo/config.toml` are copied as credential-free registry metadata; unusual manifests may require additional target handling.
22
+
23
+ ## Requirements and artifacts
24
+
25
+ No additional resource requirements or artifact globs are declared.
26
+
27
+
28
+ ## Private registries
29
+
30
+ Use `registries:` in the gitignored `.nomarmy.local.yml` only; the committed
31
+ config rejects it. Values are host credential **file paths**, never tokens.
32
+ Use `cargo: ~/.cargo/credentials.toml`. Only `cargo fetch` receives the node-owned secret mount. Configure registry names/index URLs without tokens in project metadata.
33
+
34
+ Credentials stay on the host and enter Podman only as build-secret mounts;
35
+ no credential files are copied into the context or passed to sandbox jobs.
36
+ Use a **read-only, least-privilege token** and reviewed dependencies. Secrets
37
+ are accessible to install code during that RUN, so malicious packages are not
38
+ made safe by a mount. Credential rotation invalidates both tag and install
39
+ cache. Credentialed install output/build-error details are withheld.
40
+
41
+ See [Private registries](../../docs/your-repo.md#private-registries) for the
42
+ configuration and required manual canary check: inspect `podman history
43
+ --no-trunc`, image configuration, the exported filesystem **and every saved
44
+ image layer**, plus failure logs/job records, for absent secret bytes. Unit
45
+ tests stub Podman; live isolation needs independent security review.
@@ -0,0 +1,13 @@
1
+ name: rust
2
+ summary: Rust toolchain with prefetched crates for offline verification
3
+ detect:
4
+ - file: Cargo.toml
5
+ after: []
6
+ image:
7
+ builtin: rust
8
+ verification:
9
+ quick: cargo test
10
+ artifacts: []
11
+ requires: {}
12
+ network: none
13
+ docs: README.md
package/install.sh CHANGED
@@ -72,7 +72,7 @@ MSG
72
72
  exit 1
73
73
  ;;
74
74
  *)
75
- echo "ERROR: $OS_NAME is not a supported host. See the README for supported platforms." >&2
75
+ echo "ERROR: $OS_NAME is not a supported host. See docs/install.md for supported platforms." >&2
76
76
  exit 1
77
77
  ;;
78
78
  esac
@@ -87,7 +87,9 @@ echo '==> Installing nomArmy dependencies'
87
87
 
88
88
  # Put `nomarmy` on PATH. Non-fatal by design: linking needs a writable npm
89
89
  # global prefix, and the CLI is equally usable as `node bin/nomarmy.mjs`.
90
- if (cd "$ROOT" && npm link >/dev/null 2>&1); then
90
+ if [[ "$ROOT" == */node_modules/* ]]; then
91
+ : # Installed from npm: `nomarmy` is already on PATH.
92
+ elif (cd "$ROOT" && npm link >/dev/null 2>&1); then
91
93
  echo '==> Linked the nomarmy CLI onto PATH'
92
94
  else
93
95
  echo 'NOTE: could not link the nomarmy CLI (npm global prefix not writable).'
@@ -99,10 +101,13 @@ fi
99
101
  # on a fresh macOS install nothing has initialized that VM yet at this point.
100
102
  if nomarmy_is_cloud; then need aws || { echo 'ERROR: the AWS CLI is required for cloud profiles.'; exit 1; }; fi
101
103
  "$ROOT/scripts/install-llama-cpp.sh" "$NOMARMY_PROFILE"
102
- if ! command -v openclaw >/dev/null 2>&1; then
103
- echo '==> Installing OpenClaw (non-interactive)'
104
- curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard
105
- export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
104
+ # One shared installation policy; no dist-tag or environment override.
105
+ # Only this process's PATH is changed, never a shell rc file.
106
+ export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
107
+ if [[ -w "$(npm prefix -g)" ]]; then
108
+ node "$ROOT/scripts/install-openclaw.mjs"
109
+ else
110
+ node "$ROOT/scripts/install-openclaw.mjs" --prefix "$HOME/.npm-global"
106
111
  fi
107
112
  need openclaw
108
113
  if nomarmy_has_local_model; then openclaw plugins install @openclaw/llama-cpp-provider || true; fi
@@ -111,10 +116,16 @@ if nomarmy_has_local_model; then openclaw plugins install @openclaw/llama-cpp-pr
111
116
  # Bedrock credential unless the coder sandbox is already network-isolated.
112
117
  "$ROOT/scripts/setup-sandbox.sh"
113
118
  "$ROOT/scripts/configure-openclaw.sh" "$NOMARMY_PROFILE"
114
- if [[ "$WITH_CLAUDE" == 1 ]]; then
115
- if command -v claude >/dev/null 2>&1; then node "$ROOT/bin/nomarmy.mjs" connect claude; else echo 'NOTE: Claude Code not found; worker stack installed. Install Claude Code then run: nomarmy connect claude'; fi
119
+ if [[ "${NOMARMY_COORDINATOR_OS:-}" == windows ]]; then
120
+ # Run from the Windows front end: the coordinators are Windows programs,
121
+ # registered from Windows, not looked for inside this distro.
122
+ echo 'NOTE: register your coordinators from Windows PowerShell: nomarmy connect claude (or codex, cursor)'
123
+ else
124
+ if [[ "$WITH_CLAUDE" == 1 ]]; then
125
+ if command -v claude >/dev/null 2>&1; then node "$ROOT/bin/nomarmy.mjs" connect claude; else echo 'NOTE: Claude Code not found; worker stack installed. Install Claude Code then run: nomarmy connect claude'; fi
126
+ fi
127
+ if command -v codex >/dev/null 2>&1; then node "$ROOT/bin/nomarmy.mjs" connect codex; else echo 'NOTE: Codex not found; run: nomarmy connect codex (after installing Codex)'; fi
116
128
  fi
117
- if command -v codex >/dev/null 2>&1; then node "$ROOT/bin/nomarmy.mjs" connect codex; else echo 'NOTE: Codex not found; run: nomarmy connect codex (after installing Codex)'; fi
118
129
  "$ROOT/scripts/verify-install.sh" "$NOMARMY_PROFILE"
119
130
  if nomarmy_is_cloud && [[ "${NOMARMY_ORCHESTRATOR_RUNTIME:-}" == "claude-code" ]]; then
120
131
  echo
@@ -127,3 +138,12 @@ if [[ "$(nomarmy_execution_mode)" == hosted ]]; then
127
138
  else
128
139
  echo "==> Install complete. Run: ./e2e.sh --profile $NOMARMY_PROFILE"
129
140
  fi
141
+ # Record only a completed, verified installation.
142
+ node --input-type=module - "$ROOT/package.json" "$NOMARMY_INSTALL_ROOT" "$NOMARMY_PROFILE" "$(nomarmy_execution_mode)" <<'JS'
143
+ import fs from 'node:fs';
144
+ import path from 'node:path';
145
+ const [pkg, root, profile, execution] = process.argv.slice(2);
146
+ const { version } = JSON.parse(fs.readFileSync(pkg, 'utf8'));
147
+ fs.mkdirSync(root, { recursive: true });
148
+ fs.writeFileSync(path.join(root, 'install.json'), JSON.stringify({ profile, execution, installedAt: new Date().toISOString(), version }, null, 2) + '\n');
149
+ JS