norn-cli 0.7.2__tar.gz → 0.8.0__tar.gz

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 (57) hide show
  1. norn_cli-0.8.0/PKG-INFO +293 -0
  2. norn_cli-0.8.0/README.md +268 -0
  3. {norn_cli-0.7.2 → norn_cli-0.8.0}/pyproject.toml +2 -1
  4. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/__init__.py +1 -1
  5. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/cli.py +56 -2
  6. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/config.py +176 -0
  7. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/models.py +3 -0
  8. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/pipeline.py +621 -50
  9. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/base.py +2 -0
  10. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/claude.py +2 -0
  11. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/codex.py +5 -0
  12. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/grok.py +18 -1
  13. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/reporting.py +7 -0
  14. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/default.yaml +28 -3
  15. norn_cli-0.8.0/src/norf_harness/resources/e2e_forward_command.py +162 -0
  16. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/SKILL.md +28 -0
  17. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/references/cli.md +80 -1
  18. norn_cli-0.8.0/src/norf_harness/tcp_forward.py +323 -0
  19. norn_cli-0.8.0/src/norf_harness/worktree.py +236 -0
  20. norn_cli-0.8.0/src/norn_cli.egg-info/PKG-INFO +293 -0
  21. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/SOURCES.txt +3 -0
  22. norn_cli-0.8.0/tests/test_cli.py +280 -0
  23. norn_cli-0.8.0/tests/test_config.py +283 -0
  24. norn_cli-0.8.0/tests/test_pipeline.py +925 -0
  25. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_providers.py +92 -0
  26. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_skill_install.py +24 -0
  27. norn_cli-0.8.0/tests/test_tcp_forward.py +86 -0
  28. norn_cli-0.8.0/tests/test_worktree.py +169 -0
  29. norn_cli-0.7.2/PKG-INFO +0 -160
  30. norn_cli-0.7.2/README.md +0 -135
  31. norn_cli-0.7.2/src/norf_harness/worktree.py +0 -109
  32. norn_cli-0.7.2/src/norn_cli.egg-info/PKG-INFO +0 -160
  33. norn_cli-0.7.2/tests/test_cli.py +0 -143
  34. norn_cli-0.7.2/tests/test_config.py +0 -117
  35. norn_cli-0.7.2/tests/test_pipeline.py +0 -423
  36. norn_cli-0.7.2/tests/test_worktree.py +0 -75
  37. {norn_cli-0.7.2 → norn_cli-0.8.0}/LICENSE +0 -0
  38. {norn_cli-0.7.2 → norn_cli-0.8.0}/setup.cfg +0 -0
  39. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/doctor.py +0 -0
  40. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/e2e_evidence.py +0 -0
  41. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/gate.py +0 -0
  42. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/project_gates.py +0 -0
  43. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/__init__.py +0 -0
  44. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/command.py +0 -0
  45. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/registry.py +0 -0
  46. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/router.py +0 -0
  47. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/agents/openai.yaml +0 -0
  48. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/run_store.py +0 -0
  49. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/skill_install.py +0 -0
  50. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/dependency_links.txt +0 -0
  51. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/entry_points.txt +0 -0
  52. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/requires.txt +0 -0
  53. {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/top_level.txt +0 -0
  54. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_e2e_evidence.py +0 -0
  55. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_gate.py +0 -0
  56. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_project_gates.py +0 -0
  57. {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_run_store.py +0 -0
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: norn-cli
3
+ Version: 0.8.0
4
+ Summary: Norn: a provider-neutral multi-agent development harness
5
+ License-Expression: Apache-2.0
6
+ Project-URL: Repository, https://github.com/uruca-kk/norn
7
+ Project-URL: Issues, https://github.com/uruca-kk/norn/issues
8
+ Project-URL: Changelog, https://github.com/uruca-kk/norn/blob/main/CHANGELOG.md
9
+ Keywords: agents,cli,codex,claude,testing
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: PyYAML>=6.0
20
+ Requires-Dist: jsonschema>=4.10
21
+ Provides-Extra: dev
22
+ Requires-Dist: build<2,>=1.2; extra == "dev"
23
+ Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Norn
27
+
28
+ Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
29
+ the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.
30
+
31
+ > Status: beta. The `norn-cli` distribution is published to PyPI; the source
32
+ > repository remains private during the beta period.
33
+
34
+ Norn provides configuration, CLI, run-state, event journal, recovery,
35
+ human-readable escalation reports, provider-neutral adapters, and an executable
36
+ multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
37
+ command-based subscription CLIs.
38
+
39
+ ```bash
40
+ norn init
41
+ norn --version
42
+ norn doctor
43
+ norn validate-config
44
+ norn providers
45
+ norn plan "add an audit log"
46
+ norn run "add an audit log"
47
+ ```
48
+
49
+ Install the bundled explicit-invocation skill for both Codex and Claude Code:
50
+
51
+ ```bash
52
+ norn install-skill --target all --scope project --project /path/to/repository
53
+ ```
54
+
55
+ This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
56
+ `$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
57
+ `--target codex|claude --scope user`; use `--force` only to replace an existing
58
+ copy intentionally. Loading the skill never starts a model-consuming lifecycle
59
+ unless the user explicitly requests `plan` or `run`.
60
+
61
+ See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
62
+ virtual-environment installs from PyPI.
63
+
64
+ `plan` runs orchestration, planning, and plan review. `run` continues through
65
+ implementation, language-aware local tests/lint, tester roles, triage, and final
66
+ gate. UI changes require validated E2E execution. A repository-owned managed
67
+ runner is the recommended path; the legacy policy can instead require each tester
68
+ to execute E2E independently. Every required result includes the executed argv,
69
+ exit code, a concise result summary, and at least one non-empty artifact in its
70
+ assigned evidence directory. The harness rejects missing, stale, symlinked, or
71
+ out-of-directory evidence and archives accepted files under
72
+ `.norf/runs/<run-id>/artifacts/e2e/`.
73
+
74
+ To implement a previously approved plan without invoking orchestration, planning,
75
+ or plan review again, pass the successful `plan` run ID with the identical request:
76
+
77
+ ```bash
78
+ norn plan "add an audit log"
79
+ norn run "add an audit log" --from-plan <plan-run-id>
80
+ ```
81
+
82
+ Norn verifies the source run mode and status, project root, request text, and saved
83
+ artifact schemas before creating the implementation worktree.
84
+
85
+ For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
86
+ and enables command networking through a loopback-only proxy allowlist
87
+ (`localhost` and `127.0.0.1`). Planning and implementation roles retain their
88
+ normal network-off policy.
89
+
90
+ Docker-backed tests additionally need access to the host Docker API socket. Grant
91
+ that access explicitly for one run:
92
+
93
+ ```bash
94
+ norn run "task" --allow-tester-host-services
95
+ ```
96
+
97
+ The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
98
+ Docker context, or a detected rootless, Docker Desktop, or system-default socket,
99
+ then adds only that socket as a writable root to Codex TEST invocations. Remote
100
+ TCP Docker endpoints are not added as filesystem roots. The flag does not remove
101
+ the workspace sandbox or promise that raw TCP clients can reach services bound
102
+ to the host's loopback interface. Docker socket access effectively delegates host
103
+ control, so use the flag only with a trusted repository; the resolved path and
104
+ opt-in are recorded in the run manifest and reports.
105
+
106
+ Configure one shared loopback URL for parallel browser testers. For a project-local
107
+ development server, also provide its shell-free command:
108
+
109
+ ```yaml
110
+ e2e:
111
+ base_url: http://127.0.0.1:4173
112
+ server:
113
+ command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
114
+ startup_timeout_seconds: 30
115
+ tcp_forwards:
116
+ - {listen: 4173, to: "127.0.0.1:4173"}
117
+ ```
118
+
119
+ Norn starts the command after implementation, waits for the URL, directs all tester
120
+ roles to reuse it without starting Vite, and always stops it afterward. Omit
121
+ `server.command` to reuse an already-running Docker or external Web server; external
122
+ servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
123
+ allowlist for services bound to the Norn host's loopback interface. For every E2E
124
+ command, Norn supplies a Python argv wrapper that listens only inside that command's
125
+ sandbox and exchanges byte streams through a run-scoped file spool with the host
126
+ relay. The host accepts only strictly named, regular-file requests for declared
127
+ ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
128
+ rejected. Runtime files are contained in a mode `0700` temporary directory and are
129
+ removed when testing ends.
130
+
131
+ For distributable projects, prefer a reviewed, repository-owned E2E command over
132
+ granting a model browser or host permissions:
133
+
134
+ ```yaml
135
+ e2e:
136
+ require_all_testers: false
137
+ base_url: http://127.0.0.1:4173
138
+ server:
139
+ command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
140
+ runner:
141
+ command: [pnpm, test:e2e]
142
+ ```
143
+
144
+ Norn runs the four static tester roles in parallel, starts or checks the one shared
145
+ server, then executes `runner.command` exactly once as a shell-free argv list.
146
+ The implementer may add or update E2E specs but is explicitly told not to launch a
147
+ browser, start the server, or execute this runner itself.
148
+ The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`, `NORN_RUN_ID`,
149
+ and `NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
150
+ screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
151
+ and stderr logs do not count as evidence. Norn owns the authoritative command,
152
+ timeout, exit code, and evidence validation, then gives those results to
153
+ `e2e_tester` for a read-only review with no command tools. The reviewer cannot
154
+ change a failed run into a pass. Managed runners cannot be combined with
155
+ `e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
156
+ Norn's process, outside a tester provider sandbox.
157
+
158
+ Heavy repositories receive bounded but generous defaults: model roles and project
159
+ test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
160
+ Override them per project when needed:
161
+
162
+ ```yaml
163
+ limits:
164
+ timeouts:
165
+ role_seconds: 3600
166
+ test_seconds: 3600
167
+ e2e_seconds: 2400
168
+ ```
169
+
170
+ These are timeout ceilings, not delays. Existing project configurations that
171
+ explicitly contain older values keep those values until updated.
172
+
173
+ The default `require_all_testers: true` keeps independent browser evidence from
174
+ spec, clean, security, and chaos testers. Projects where parallel quality commands
175
+ interfere with a watched development server can opt into one dedicated browser role:
176
+
177
+ ```yaml
178
+ e2e:
179
+ require_all_testers: false
180
+ roles:
181
+ e2e_tester: {profile: e2e-browser}
182
+ ```
183
+
184
+ Norn first runs the four non-browser tester roles in parallel, then starts or checks
185
+ the shared server and runs `e2e_tester` alone. The dedicated role is still required
186
+ to produce valid browser evidence. Its profile is independently configurable, so a
187
+ non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
188
+ sandbox. In this mode the four static roles are explicitly prohibited from starting a
189
+ browser or development server, connecting to the E2E URL, or rerunning shared project-wide
190
+ lint, test, coverage, and build commands already owned by Norn's deterministic gate.
191
+
192
+ The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
193
+ medium effort as its fallback. It intentionally has no Codex or Fable candidate.
194
+ When no managed runner is configured and the provider's non-interactive permission
195
+ policy still blocks browser or loopback access, a trusted repository may opt in at run time with
196
+ `--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
197
+ `e2e_tester`; the four static testers keep their normal TEST sandbox.
198
+
199
+ Profiles keep a stable logical model name while optionally mapping it to a
200
+ provider-specific identifier. For example, Codex Sol is configured as:
201
+
202
+ ```yaml
203
+ candidates:
204
+ - provider: codex-cli
205
+ model: sol
206
+ model_id: gpt-5.6-sol
207
+ effort: high
208
+ ```
209
+
210
+ Round limits are configured under `limits` or overridden per invocation:
211
+
212
+ ```bash
213
+ norn run "task" --max-rounds 5 --max-total-rounds 12 \
214
+ --max-planner-retries 2 --max-final-replans 2
215
+ ```
216
+
217
+ Known Python, Node, Rust, and Go gates are auto-detected. Projects can define
218
+ additional commands as shell-free argv lists:
219
+
220
+ ```yaml
221
+ gates:
222
+ enabled: true
223
+ auto_detect: true
224
+ commands:
225
+ - name: integration
226
+ kind: test
227
+ command: [python3, -m, pytest, tests/integration]
228
+ ```
229
+
230
+ Each run records role responses, model selections, attempts, findings, test
231
+ results, and round history below `.norf/runs/<run-id>/`. Any non-successful stop
232
+ also produces `escalation-report.md` and `escalation.json` for human judgment.
233
+
234
+ By default, `run` requires a clean Git repository and implements inside a
235
+ detached `.norf/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
236
+ direct changes to the selected project directory are intentional.
237
+
238
+ Fresh Git worktrees contain tracked files only. Projects whose tests require
239
+ ignored dependencies, local environment files, or a prepared database can
240
+ provision the worktree before any provider role starts:
241
+
242
+ ```yaml
243
+ git:
244
+ worktree: true
245
+ worktree_setup:
246
+ - name: php-dependencies
247
+ copy: laravel/vendor
248
+ - name: node-dependencies
249
+ copy: node_modules
250
+ optional: true
251
+ - name: test-database
252
+ command: [scripts/setup-norn-database, --template]
253
+ timeout_seconds: 900
254
+ ```
255
+
256
+ Each step has exactly one action. `copy` materializes a project-relative path
257
+ from the source checkout; `command` is a shell-free argv list executed in the
258
+ new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
259
+ `NORN_RUN_ID`. A failed required step stops the run before model invocation.
260
+
261
+ ## Development and packaging
262
+
263
+ Install the local development tools and run the same checks as CI:
264
+
265
+ ```bash
266
+ python3 -m pip install --editable ".[dev]"
267
+ ruff format --check .
268
+ ruff check .
269
+ python3 -m unittest discover -v
270
+ python3 -m build
271
+ python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
272
+ ```
273
+
274
+ CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
275
+ built wheel in a clean virtual environment and verifies both Codex and Claude
276
+ skill copies. It uploads the distributions as a short-lived workflow artifact.
277
+ Publishing a GitHub Release runs the same deterministic gates before sending
278
+ the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
279
+ invokes subscription models.
280
+
281
+ Release acceptance is intentionally split in two: CI performs deterministic
282
+ package checks, while local acceptance invokes the authenticated subscription
283
+ CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
284
+ See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
285
+ private vulnerability reporting.
286
+
287
+ The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
288
+ the pre-1.0 migration; new automation should use `norn`.
289
+
290
+ ## License
291
+
292
+ Copyright 2026 uruca-kk. Licensed under the
293
+ [Apache License 2.0](LICENSE).
@@ -0,0 +1,268 @@
1
+ # Norn
2
+
3
+ Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
4
+ the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.
5
+
6
+ > Status: beta. The `norn-cli` distribution is published to PyPI; the source
7
+ > repository remains private during the beta period.
8
+
9
+ Norn provides configuration, CLI, run-state, event journal, recovery,
10
+ human-readable escalation reports, provider-neutral adapters, and an executable
11
+ multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
12
+ command-based subscription CLIs.
13
+
14
+ ```bash
15
+ norn init
16
+ norn --version
17
+ norn doctor
18
+ norn validate-config
19
+ norn providers
20
+ norn plan "add an audit log"
21
+ norn run "add an audit log"
22
+ ```
23
+
24
+ Install the bundled explicit-invocation skill for both Codex and Claude Code:
25
+
26
+ ```bash
27
+ norn install-skill --target all --scope project --project /path/to/repository
28
+ ```
29
+
30
+ This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
31
+ `$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
32
+ `--target codex|claude --scope user`; use `--force` only to replace an existing
33
+ copy intentionally. Loading the skill never starts a model-consuming lifecycle
34
+ unless the user explicitly requests `plan` or `run`.
35
+
36
+ See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
37
+ virtual-environment installs from PyPI.
38
+
39
+ `plan` runs orchestration, planning, and plan review. `run` continues through
40
+ implementation, language-aware local tests/lint, tester roles, triage, and final
41
+ gate. UI changes require validated E2E execution. A repository-owned managed
42
+ runner is the recommended path; the legacy policy can instead require each tester
43
+ to execute E2E independently. Every required result includes the executed argv,
44
+ exit code, a concise result summary, and at least one non-empty artifact in its
45
+ assigned evidence directory. The harness rejects missing, stale, symlinked, or
46
+ out-of-directory evidence and archives accepted files under
47
+ `.norf/runs/<run-id>/artifacts/e2e/`.
48
+
49
+ To implement a previously approved plan without invoking orchestration, planning,
50
+ or plan review again, pass the successful `plan` run ID with the identical request:
51
+
52
+ ```bash
53
+ norn plan "add an audit log"
54
+ norn run "add an audit log" --from-plan <plan-run-id>
55
+ ```
56
+
57
+ Norn verifies the source run mode and status, project root, request text, and saved
58
+ artifact schemas before creating the implementation worktree.
59
+
60
+ For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
61
+ and enables command networking through a loopback-only proxy allowlist
62
+ (`localhost` and `127.0.0.1`). Planning and implementation roles retain their
63
+ normal network-off policy.
64
+
65
+ Docker-backed tests additionally need access to the host Docker API socket. Grant
66
+ that access explicitly for one run:
67
+
68
+ ```bash
69
+ norn run "task" --allow-tester-host-services
70
+ ```
71
+
72
+ The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
73
+ Docker context, or a detected rootless, Docker Desktop, or system-default socket,
74
+ then adds only that socket as a writable root to Codex TEST invocations. Remote
75
+ TCP Docker endpoints are not added as filesystem roots. The flag does not remove
76
+ the workspace sandbox or promise that raw TCP clients can reach services bound
77
+ to the host's loopback interface. Docker socket access effectively delegates host
78
+ control, so use the flag only with a trusted repository; the resolved path and
79
+ opt-in are recorded in the run manifest and reports.
80
+
81
+ Configure one shared loopback URL for parallel browser testers. For a project-local
82
+ development server, also provide its shell-free command:
83
+
84
+ ```yaml
85
+ e2e:
86
+ base_url: http://127.0.0.1:4173
87
+ server:
88
+ command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
89
+ startup_timeout_seconds: 30
90
+ tcp_forwards:
91
+ - {listen: 4173, to: "127.0.0.1:4173"}
92
+ ```
93
+
94
+ Norn starts the command after implementation, waits for the URL, directs all tester
95
+ roles to reuse it without starting Vite, and always stops it afterward. Omit
96
+ `server.command` to reuse an already-running Docker or external Web server; external
97
+ servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
98
+ allowlist for services bound to the Norn host's loopback interface. For every E2E
99
+ command, Norn supplies a Python argv wrapper that listens only inside that command's
100
+ sandbox and exchanges byte streams through a run-scoped file spool with the host
101
+ relay. The host accepts only strictly named, regular-file requests for declared
102
+ ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
103
+ rejected. Runtime files are contained in a mode `0700` temporary directory and are
104
+ removed when testing ends.
105
+
106
+ For distributable projects, prefer a reviewed, repository-owned E2E command over
107
+ granting a model browser or host permissions:
108
+
109
+ ```yaml
110
+ e2e:
111
+ require_all_testers: false
112
+ base_url: http://127.0.0.1:4173
113
+ server:
114
+ command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
115
+ runner:
116
+ command: [pnpm, test:e2e]
117
+ ```
118
+
119
+ Norn runs the four static tester roles in parallel, starts or checks the one shared
120
+ server, then executes `runner.command` exactly once as a shell-free argv list.
121
+ The implementer may add or update E2E specs but is explicitly told not to launch a
122
+ browser, start the server, or execute this runner itself.
123
+ The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`, `NORN_RUN_ID`,
124
+ and `NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
125
+ screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
126
+ and stderr logs do not count as evidence. Norn owns the authoritative command,
127
+ timeout, exit code, and evidence validation, then gives those results to
128
+ `e2e_tester` for a read-only review with no command tools. The reviewer cannot
129
+ change a failed run into a pass. Managed runners cannot be combined with
130
+ `e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
131
+ Norn's process, outside a tester provider sandbox.
132
+
133
+ Heavy repositories receive bounded but generous defaults: model roles and project
134
+ test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
135
+ Override them per project when needed:
136
+
137
+ ```yaml
138
+ limits:
139
+ timeouts:
140
+ role_seconds: 3600
141
+ test_seconds: 3600
142
+ e2e_seconds: 2400
143
+ ```
144
+
145
+ These are timeout ceilings, not delays. Existing project configurations that
146
+ explicitly contain older values keep those values until updated.
147
+
148
+ The default `require_all_testers: true` keeps independent browser evidence from
149
+ spec, clean, security, and chaos testers. Projects where parallel quality commands
150
+ interfere with a watched development server can opt into one dedicated browser role:
151
+
152
+ ```yaml
153
+ e2e:
154
+ require_all_testers: false
155
+ roles:
156
+ e2e_tester: {profile: e2e-browser}
157
+ ```
158
+
159
+ Norn first runs the four non-browser tester roles in parallel, then starts or checks
160
+ the shared server and runs `e2e_tester` alone. The dedicated role is still required
161
+ to produce valid browser evidence. Its profile is independently configurable, so a
162
+ non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
163
+ sandbox. In this mode the four static roles are explicitly prohibited from starting a
164
+ browser or development server, connecting to the E2E URL, or rerunning shared project-wide
165
+ lint, test, coverage, and build commands already owned by Norn's deterministic gate.
166
+
167
+ The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
168
+ medium effort as its fallback. It intentionally has no Codex or Fable candidate.
169
+ When no managed runner is configured and the provider's non-interactive permission
170
+ policy still blocks browser or loopback access, a trusted repository may opt in at run time with
171
+ `--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
172
+ `e2e_tester`; the four static testers keep their normal TEST sandbox.
173
+
174
+ Profiles keep a stable logical model name while optionally mapping it to a
175
+ provider-specific identifier. For example, Codex Sol is configured as:
176
+
177
+ ```yaml
178
+ candidates:
179
+ - provider: codex-cli
180
+ model: sol
181
+ model_id: gpt-5.6-sol
182
+ effort: high
183
+ ```
184
+
185
+ Round limits are configured under `limits` or overridden per invocation:
186
+
187
+ ```bash
188
+ norn run "task" --max-rounds 5 --max-total-rounds 12 \
189
+ --max-planner-retries 2 --max-final-replans 2
190
+ ```
191
+
192
+ Known Python, Node, Rust, and Go gates are auto-detected. Projects can define
193
+ additional commands as shell-free argv lists:
194
+
195
+ ```yaml
196
+ gates:
197
+ enabled: true
198
+ auto_detect: true
199
+ commands:
200
+ - name: integration
201
+ kind: test
202
+ command: [python3, -m, pytest, tests/integration]
203
+ ```
204
+
205
+ Each run records role responses, model selections, attempts, findings, test
206
+ results, and round history below `.norf/runs/<run-id>/`. Any non-successful stop
207
+ also produces `escalation-report.md` and `escalation.json` for human judgment.
208
+
209
+ By default, `run` requires a clean Git repository and implements inside a
210
+ detached `.norf/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
211
+ direct changes to the selected project directory are intentional.
212
+
213
+ Fresh Git worktrees contain tracked files only. Projects whose tests require
214
+ ignored dependencies, local environment files, or a prepared database can
215
+ provision the worktree before any provider role starts:
216
+
217
+ ```yaml
218
+ git:
219
+ worktree: true
220
+ worktree_setup:
221
+ - name: php-dependencies
222
+ copy: laravel/vendor
223
+ - name: node-dependencies
224
+ copy: node_modules
225
+ optional: true
226
+ - name: test-database
227
+ command: [scripts/setup-norn-database, --template]
228
+ timeout_seconds: 900
229
+ ```
230
+
231
+ Each step has exactly one action. `copy` materializes a project-relative path
232
+ from the source checkout; `command` is a shell-free argv list executed in the
233
+ new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
234
+ `NORN_RUN_ID`. A failed required step stops the run before model invocation.
235
+
236
+ ## Development and packaging
237
+
238
+ Install the local development tools and run the same checks as CI:
239
+
240
+ ```bash
241
+ python3 -m pip install --editable ".[dev]"
242
+ ruff format --check .
243
+ ruff check .
244
+ python3 -m unittest discover -v
245
+ python3 -m build
246
+ python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
247
+ ```
248
+
249
+ CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
250
+ built wheel in a clean virtual environment and verifies both Codex and Claude
251
+ skill copies. It uploads the distributions as a short-lived workflow artifact.
252
+ Publishing a GitHub Release runs the same deterministic gates before sending
253
+ the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
254
+ invokes subscription models.
255
+
256
+ Release acceptance is intentionally split in two: CI performs deterministic
257
+ package checks, while local acceptance invokes the authenticated subscription
258
+ CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
259
+ See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
260
+ private vulnerability reporting.
261
+
262
+ The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
263
+ the pre-1.0 migration; new automation should use `norn`.
264
+
265
+ ## License
266
+
267
+ Copyright 2026 uruca-kk. Licensed under the
268
+ [Apache License 2.0](LICENSE).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "norn-cli"
7
- version = "0.7.2"
7
+ version = "0.8.0"
8
8
  description = "Norn: a provider-neutral multi-agent development harness"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -49,6 +49,7 @@ where = ["src"]
49
49
  [tool.setuptools.package-data]
50
50
  norf_harness = [
51
51
  "resources/*.yaml",
52
+ "resources/*.py",
52
53
  "resources/skills/norn/*.md",
53
54
  "resources/skills/norn/agents/*.yaml",
54
55
  "resources/skills/norn/references/*.md",
@@ -3,4 +3,4 @@
3
3
  from .config import HarnessConfig, load_config
4
4
 
5
5
  __all__ = ["HarnessConfig", "load_config"]
6
- __version__ = "0.7.2"
6
+ __version__ = "0.8.0"
@@ -66,6 +66,16 @@ def build_parser() -> argparse.ArgumentParser:
66
66
  metavar="RUN_ID",
67
67
  help="reuse the approved plan from a successful plan run",
68
68
  )
69
+ run_parser.add_argument(
70
+ "--allow-tester-host-services",
71
+ action="store_true",
72
+ help="allow tester roles to use supported host test services such as Docker",
73
+ )
74
+ run_parser.add_argument(
75
+ "--allow-e2e-danger-full-access",
76
+ action="store_true",
77
+ help="run only the dedicated e2e_tester without provider sandbox restrictions",
78
+ )
69
79
  _common_options(run_parser)
70
80
  _limit_options(run_parser)
71
81
 
@@ -121,6 +131,8 @@ def main(argv: list[str] | None = None) -> int:
121
131
  args.request,
122
132
  RunMode(args.command),
123
133
  plan_run_id=getattr(args, "plan_run_id", None),
134
+ allow_tester_host_services=getattr(args, "allow_tester_host_services", False),
135
+ allow_e2e_danger_full_access=getattr(args, "allow_e2e_danger_full_access", False),
124
136
  )
125
137
  if args.command == "status":
126
138
  return _status(config, args.run_id, args.json_output)
@@ -242,8 +254,50 @@ def _providers(config, json_output: bool) -> int:
242
254
  return 1 if failed else 0
243
255
 
244
256
 
245
- def _execution(config, request: str, mode: RunMode, *, plan_run_id: str | None = None) -> int:
246
- outcome = asyncio.run(execute_pipeline(config, request, mode, plan_run_id=plan_run_id))
257
+ def _execution(
258
+ config,
259
+ request: str,
260
+ mode: RunMode,
261
+ *,
262
+ plan_run_id: str | None = None,
263
+ allow_tester_host_services: bool = False,
264
+ allow_e2e_danger_full_access: bool = False,
265
+ ) -> int:
266
+ if allow_e2e_danger_full_access and config.e2e_require_all_testers:
267
+ print(
268
+ "CONFIG ERROR: --allow-e2e-danger-full-access requires e2e.require_all_testers: false",
269
+ file=sys.stderr,
270
+ )
271
+ return int(ExitCode.CONFIG_ERROR)
272
+ if allow_e2e_danger_full_access and config.e2e_runner_command:
273
+ print(
274
+ "CONFIG ERROR: --allow-e2e-danger-full-access cannot be used with "
275
+ "e2e.runner.command; the managed runner does not delegate execution to a model",
276
+ file=sys.stderr,
277
+ )
278
+ return int(ExitCode.CONFIG_ERROR)
279
+ if allow_tester_host_services:
280
+ print(
281
+ "WARNING: tester roles may access the host Docker API; "
282
+ "only use this with trusted repositories.",
283
+ file=sys.stderr,
284
+ )
285
+ if allow_e2e_danger_full_access:
286
+ print(
287
+ "WARNING: the dedicated e2e_tester runs with provider permission checks and "
288
+ "sandbox restrictions bypassed; only use this with a trusted repository.",
289
+ file=sys.stderr,
290
+ )
291
+ outcome = asyncio.run(
292
+ execute_pipeline(
293
+ config,
294
+ request,
295
+ mode,
296
+ plan_run_id=plan_run_id,
297
+ allow_tester_host_services=allow_tester_host_services,
298
+ allow_e2e_danger_full_access=allow_e2e_danger_full_access,
299
+ )
300
+ )
247
301
  manifest = outcome.manifest
248
302
  if manifest.exit_code == 0:
249
303
  print(f"Harness succeeded: {outcome.paths.final_report}")