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.
- package/README.md +86 -480
- package/bin/nomarmy.mjs +1081 -185
- package/docker/Dockerfile +2 -2
- package/docker/Dockerfile.go +6 -4
- package/docker/Dockerfile.rust +17 -2
- package/harnesses/_template/README.md +27 -0
- package/harnesses/_template/harness.yml +26 -0
- package/harnesses/browser-playwright/README.md +35 -0
- package/harnesses/browser-playwright/fixture/package.json +1 -0
- package/harnesses/browser-playwright/fixture/page.html +1 -0
- package/harnesses/browser-playwright/fixture/page.spec.js +5 -0
- package/harnesses/browser-playwright/fixture/playwright.config.js +8 -0
- package/harnesses/browser-playwright/harness.yml +18 -0
- package/harnesses/go/README.md +45 -0
- package/harnesses/go/harness.yml +14 -0
- package/harnesses/mock-oidc/README.md +31 -0
- package/harnesses/mock-oidc/fixture/.nomarmy.yml +4 -0
- package/harnesses/mock-oidc/fixture/discovery.test.mjs +16 -0
- package/harnesses/mock-oidc/harness.yml +19 -0
- package/harnesses/node/README.md +53 -0
- package/harnesses/node/harness.yml +18 -0
- package/harnesses/python/README.md +46 -0
- package/harnesses/python/harness.yml +16 -0
- package/harnesses/rust/README.md +45 -0
- package/harnesses/rust/harness.yml +13 -0
- package/install.sh +29 -9
- package/lib/admission.mjs +178 -30
- package/lib/agents.mjs +8 -6
- package/lib/army.mjs +25 -10
- package/lib/codex-link.mjs +37 -0
- package/lib/config.mjs +15 -0
- package/lib/connect.mjs +232 -19
- package/lib/continue-from.mjs +103 -0
- package/lib/coordinator-instructions.mjs +5 -1
- package/lib/diff-checks.mjs +114 -0
- package/lib/dispatch-schema.mjs +14 -12
- package/lib/doctor.mjs +98 -9
- package/lib/egress-proxy.mjs +116 -0
- package/lib/execute.mjs +241 -33
- package/lib/git-record.mjs +27 -3
- package/lib/harness-schema.mjs +61 -0
- package/lib/harnesses.mjs +99 -0
- package/lib/health.mjs +162 -18
- package/lib/install-freshness.mjs +114 -0
- package/lib/jev-checks.mjs +110 -0
- package/lib/job-format.mjs +54 -0
- package/lib/judge.mjs +130 -0
- package/lib/limits.mjs +77 -0
- package/lib/model-probe.mjs +61 -0
- package/lib/mutation.mjs +159 -0
- package/lib/notify.mjs +30 -3
- package/lib/openclaw-install.mjs +122 -0
- package/lib/openclaw-path.mjs +28 -0
- package/lib/openclaw-run.mjs +74 -12
- package/lib/openclaw-runtime-health.mjs +56 -0
- package/lib/outcome.mjs +21 -2
- package/lib/outcomes.mjs +6 -0
- package/lib/path-utils.mjs +4 -0
- package/lib/podman-health.mjs +41 -0
- package/lib/process.mjs +4 -1
- package/lib/propose.mjs +10 -11
- package/lib/refusal-retry.mjs +16 -0
- package/lib/registry-python.mjs +98 -0
- package/lib/registry-secrets.mjs +140 -0
- package/lib/repo-query.mjs +13 -7
- package/lib/runs.mjs +7 -1
- package/lib/same-path.mjs +14 -0
- package/lib/sandbox-images.mjs +499 -83
- package/lib/sandbox-vm.mjs +32 -0
- package/lib/scan.mjs +5 -1
- package/lib/schema.mjs +20 -11
- package/lib/scout.mjs +21 -3
- package/lib/server-context.mjs +21 -1
- package/lib/setup-steps.mjs +55 -0
- package/lib/share.mjs +82 -0
- package/lib/stale-sessions.mjs +60 -0
- package/lib/stats.mjs +315 -0
- package/lib/statusline.mjs +32 -6
- package/lib/subscription-setup.mjs +13 -0
- package/lib/suggestions.mjs +153 -0
- package/lib/thinking.mjs +23 -0
- package/lib/transcript.mjs +30 -5
- package/lib/usage-limits.mjs +329 -0
- package/lib/user-config.mjs +106 -0
- package/lib/validators.mjs +220 -0
- package/lib/verification-artifacts.mjs +46 -0
- package/lib/verification-flow.mjs +52 -7
- package/lib/verification-network.mjs +66 -0
- package/lib/verify.mjs +338 -85
- package/lib/worker-prompt.mjs +5 -2
- package/lib/wsl-cli.mjs +152 -0
- package/lib/wsl.mjs +230 -0
- package/lib/zod-issues.mjs +15 -0
- package/mcp/server.mjs +165 -34
- package/package.json +7 -5
- package/playbooks/feature.md +8 -5
- package/scripts/configure-openclaw.sh +4 -2
- package/scripts/generate-harness-docs.mjs +42 -0
- package/scripts/install-openclaw.mjs +23 -0
- package/scripts/lib.sh +9 -2
- package/scripts/select-model.mjs +12 -5
- package/scripts/start-inference.sh +2 -2
package/docker/Dockerfile
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
FROM node:
|
|
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
|
|
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 --
|
package/docker/Dockerfile.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM node:
|
|
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}"
|
package/docker/Dockerfile.rust
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM node:
|
|
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
|
-
|
|
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,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,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
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 [[ "$
|
|
115
|
-
|
|
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
|