Metadata-Version: 2.4
Name: norn-cli
Version: 0.11.0
Summary: Norn: a provider-neutral multi-agent development harness
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/uruca-kk/norn
Project-URL: Issues, https://github.com/uruca-kk/norn/issues
Project-URL: Changelog, https://github.com/uruca-kk/norn/blob/main/CHANGELOG.md
Keywords: agents,cli,codex,claude,testing
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Requires-Dist: jsonschema>=4.10
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == "dev"
Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
Dynamic: license-file

# Norn

Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.

> Status: beta. The `norn-cli` distribution is published to PyPI; the source
> repository remains private during the beta period.

Norn provides configuration, CLI, run-state, event journal, recovery,
human-readable escalation reports, provider-neutral adapters, and an executable
multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
command-based subscription CLIs.

```bash
norn init
norn --version
norn doctor
norn validate-config
norn providers
norn models init
norn models list
norn verify plan --no-agent
norn verify run
norn plan "add an audit log"
norn run "add an audit log"
norn run "migrate authentication data safely" --team auto
norn recover <run-id>
norn continue <run-id> --execution <execution-id>
```

`plan` and `run` immediately print the Run ID and stream append-only lifecycle progress to stderr:
current phase, bounded round counts, role start/completion, local gates, E2E, quality decisions, and
the terminal outcome. Prompts and private model reasoning are not printed. Lifecycle state and
events commit transactionally to `.norn/runs/state.sqlite3` and are projected to each run's
`manifest.json` and `events.jsonl`; use `--no-progress` to suppress only the live stderr rendering
for automation.

New projects use `.norn` for configuration and run state. Repositories initialized by an older
Norn release continue using `.norf` without splitting their state until explicitly migrated:

```bash
norn migrate --project /path/to/repository
```

Migration preserves terminal run records and updates the shipped `.norf/runs` config value. It
refuses ambiguous `.norn`/`.norf` coexistence, active runs, symlinks, and existing detached
worktrees; finish or recover runs and remove registered worktrees before retrying. If the
user-level config explicitly sets `paths.runs_dir: .norf/runs`, update that shared setting first.

Install the bundled explicit-invocation skill for both Codex and Claude Code:

```bash
norn install-skill --target all --scope project --project /path/to/repository
```

This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
`$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
`--target codex|claude --scope user`; use `--force` only to replace an existing
copy intentionally. Loading the skill never starts a model-consuming lifecycle
unless the user explicitly requests `plan` or `run`.

See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
virtual-environment installs from PyPI.

`plan` runs orchestration, planning, and plan review. `run` continues through
implementation, language-aware local tests/lint, tester roles, triage, and final
gate. Tester roles start only after every local project gate passes; a failed gate
returns directly to triage and reimplementation, while an unavailable gate stops
as a test-infrastructure failure. UI changes require validated E2E execution. A
repository-owned managed runner is the recommended path; the legacy policy can
instead require each tester to execute E2E independently. Every required result
includes the executed argv, exit code, a concise result summary, and at least one
non-empty artifact in its
assigned evidence directory. The harness rejects missing, stale, symlinked, or
out-of-directory evidence and archives accepted files under
`.norn/runs/<run-id>/artifacts/e2e/`.

After triage requests reimplementation, Norn fingerprints the Git workspace around
each provider attempt. An attempt that changes neither tracked content or mode nor
non-ignored untracked files is recorded as `implementation.noop` and rejected before
local gates, tester roles, or E2E run again. The existing provider retry and fallback
limits apply, and the next attempt receives the no-op diagnostic. An initial
implementation may still be a legitimate no-op when the requested behavior already
exists.

To implement a previously approved plan without invoking orchestration, planning,
or plan review again, pass the successful `plan` run ID with the identical request:

```bash
norn plan "add an audit log"
norn run "add an audit log" --from-plan <plan-run-id>
```

Norn verifies the source run mode and status, project root, request text, and saved
artifact schemas before creating the implementation worktree.

For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
and enables command networking through a loopback-only proxy allowlist
(`localhost` and `127.0.0.1`). Planning and implementation roles retain their
normal network-off policy.

Docker-backed tests additionally need access to the host Docker API socket. Grant
that access explicitly for one run:

```bash
norn run "task" --allow-tester-host-services
```

The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
Docker context, or a detected rootless, Docker Desktop, or system-default socket,
then adds only that socket as a writable root to Codex TEST invocations. Remote
TCP Docker endpoints are not added as filesystem roots. The flag does not remove
the workspace sandbox or promise that raw TCP clients can reach services bound
to the host's loopback interface. Docker socket access effectively delegates host
control, so use the flag only with a trusted repository; the resolved path and
opt-in are recorded in the run manifest and reports.

Configure one shared loopback URL for parallel browser testers. For a project-local
development server, also provide its shell-free command:

```yaml
e2e:
  base_url: http://127.0.0.1:4173
  server:
    command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
    startup_timeout_seconds: 30
  tcp_forwards:
    - {listen: 4173, to: "127.0.0.1:4173"}
```

Norn starts the command after implementation, waits for the URL, directs all tester
roles to reuse it without starting Vite, and always stops it afterward. Omit
`server.command` to reuse an already-running Docker or external Web server; external
servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
allowlist for services bound to the Norn host's loopback interface. For every E2E
command, Norn supplies a Python argv wrapper that listens only inside that command's
sandbox and exchanges byte streams through a run-scoped file spool with the host
relay. The host accepts only strictly named, regular-file requests for declared
ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
rejected. Runtime files are contained in a mode `0700` temporary directory and are
removed when testing ends.

For distributable projects, prefer a reviewed, repository-owned E2E command over
granting a model browser or host permissions:

```bash
# Adapt the one detected Playwright/Cypress suite. This is a dry run.
norn init-e2e --adapter existing --project /path/to/repository

# Or scaffold a new Playwright entrypoint in a selected workspace package.
norn init-e2e --preset playwright --package packages/web --project /path/to/repository

# Or scaffold a deterministic Agent Browser smoke runner without Playwright.
norn init-e2e --preset agent-browser --package packages/web --project /path/to/repository
```

`init-e2e` detects npm, pnpm, yarn, or bun and common E2E scripts/configs. It prints
the files, minimal Norn YAML, evidence limits, and an optional `git.worktree_setup`
copy step without changing the repository. Add `--write` after review. Use
`--external-server` for an already-running Docker/external server, or
`--server-script NAME` when the project script is not `dev`, `start`, or `serve`.
`--base-url` must be a loopback HTTP(S) URL.

The existing-suite adapter generates a repository-owned runner and, for Playwright,
a small config that extends the existing config while disabling its `webServer` so
Norn remains the only server owner. The Playwright preset additionally creates a
bounded evidence helper, a smoke spec, a JSON reporter, and low-volume artifact
defaults. It does not install Playwright unless `--install-dependency` is explicitly
combined with `--write`; existing framework versions and fixtures remain owned by
the project.

The `agent-browser` preset instead generates a safe wrapper plus a user-owned
`tests/e2e/norn-agent-browser.json` shell-free command sequence. Each run uses an isolated session
and a loopback-only domain allowlist, opens the configured URL, captures an accessibility snapshot
and full-page screenshot, writes a structured result, and always closes its session. Customize the
JSON sequence with app-specific semantic `find`/`click`/`fill`/`wait` assertions; scaffold reruns
and `--force` preserve that scenario. Install Agent Browser and its Chromium separately with
`npm install -g agent-browser && agent-browser install`; `init-e2e --check` verifies the command
without launching a browser.

Re-running `--write` is idempotent. Existing generated files, package scripts, or
Norn config values are not replaced when they differ; `--force` is the explicit
migration path. Validate the checked-in contract without launching a browser:

```bash
norn init-e2e --adapter existing --external-server --check --project /path/to/repository
norn init-e2e --preset playwright --package packages/web --check --project /path/to/repository
```

The generated runner receives `NORN_E2E_BASE_URL` and
`NORN_E2E_EVIDENCE_DIR`, writes its own result record, and keeps evidence within
the configured file-count and total-size limits. Its corresponding configuration is a shell-free
argv list:

```yaml
e2e:
  require_all_testers: false
  evidence:
    max_files: 100
    max_total_mib: 128
  base_url: http://127.0.0.1:4173
  server:
    command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
  runner:
    command: [pnpm, test:e2e]
```

`e2e.evidence.max_files` defaults to 20 and `e2e.evidence.max_total_mib` defaults to 64; both must
be positive integers. Set them in `.norn/harness.yaml` for persistent project limits, or override
them for one invocation with
`norn run "task" --max-evidence-files 100 --max-evidence-mib 128`. Norn passes the resolved values
to managed runners as `NORN_E2E_MAX_EVIDENCE_FILES` and `NORN_E2E_MAX_EVIDENCE_BYTES`; delegated
tester roles receive the same limits in their instruction payload.

When the selected template includes the four static tester roles, Norn runs them in parallel,
starts or checks the one shared server, then executes `runner.command` exactly once as a
shell-free argv list.
The implementer may add or update E2E specs but is explicitly told not to launch a
browser, start the server, or execute this runner itself.
The implementer writes its detailed outcome as a run-scoped Markdown artifact and
returns only a small structured control object containing `status` and the assigned
artifact path. Norn rejects missing, empty, stale, symlinked, hard-linked, oversized,
or out-of-contract reports before accepting the provider attempt; changed files are
derived from Git rather than trusted from the model response.
The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`,
`NORN_E2E_MAX_EVIDENCE_FILES`, `NORN_E2E_MAX_EVIDENCE_BYTES`, `NORN_RUN_ID`, and
`NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
and stderr logs do not count as evidence. Norn owns the authoritative command,
timeout, exit code, and evidence validation, then gives those results to
`e2e_tester` for a read-only review with no command tools. The reviewer returns
only its summary and findings; Norn injects the authoritative test and E2E
records into the run artifact. Likewise, static testers do not echo a fixed E2E
placeholder or shared gate results in their structured responses. The reviewer
cannot change a failed run into a pass. Managed runners cannot be combined with
`e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
Norn's process, outside a tester provider sandbox.

Heavy repositories receive bounded but generous defaults: model roles and project
test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
Override them per project when needed:

```yaml
limits:
  timeouts:
    role_seconds: 3600
    test_seconds: 3600
    e2e_seconds: 2400
```

These are timeout ceilings, not delays. Existing project configurations that
explicitly contain older values keep those values until updated.

The default `require_all_testers: true` keeps independent browser evidence from
spec, clean, security, and chaos testers. Projects where parallel quality commands
interfere with a watched development server can opt into one dedicated browser role:

```yaml
e2e:
  require_all_testers: false
roles:
  e2e_tester: {profile: e2e-browser}
```

When the selected template includes the four non-browser tester roles, Norn first runs them in
parallel, then starts or checks the shared server and runs `e2e_tester` alone. The dedicated role is
still required
to produce valid browser evidence. Its profile is independently configurable, so a
non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
sandbox. In this mode all selected static roles are explicitly prohibited from starting a
browser or development server, connecting to the E2E URL, or rerunning shared project-wide
lint, test, coverage, and build commands already owned by Norn's deterministic gate.

The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
medium effort as its fallback. It intentionally has no Codex or Fable candidate.
Sandboxed Grok roles fail closed and move to the next provider when Grok cannot prove that its OS
sandbox was enforced. On a trusted headless host affected by Grok's Linux `/dev/tty` Landlock
error, `GROK_SANDBOX=off norn run "task"` is an explicit process-scoped bypass for Grok roles.
Norn still uses non-interactive permission modes and disables Git, GPG, credential, editor, and
pager prompts, but kernel-level filesystem isolation is absent; do not use this opt-in for an
untrusted repository.
When no managed runner is configured and the provider's non-interactive permission
policy still blocks browser or loopback access, a trusted repository may opt in at run time with
`--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
`e2e_tester`; the selected static testers keep their normal TEST sandbox.

Models keep a stable logical reference while optionally mapping it to a provider-specific
identifier. Profiles reference the model and select an effort:

```yaml
models:
  codex-sol:
    provider: codex-cli
    model: sol
    model_id: gpt-5.6-sol
    enabled: true
    efforts: [high, max]
    capabilities: [planning, coding, review, structured-output]

profiles:
  sol-high:
    candidates:
      - model_ref: codex-sol
        effort: high
```

Legacy inline candidates remain accepted during the pre-1.0 migration. `norn models init`
creates or safely merges a user-scoped observed registry. `norn models refresh` repeats native
provider discovery without deleting missing entries, and `norn models list --json` exposes the
effective inventory. Native discovery does not invoke a model; providers without a listing
command remain `declared` until explicitly verified.

`norn models probe codex-sol` is the separate, model-consuming verification path. It invokes only
the exact enabled configured reference, once, at its lowest configured effort, with no retry or
fallback and no repository context. Multiple references may be named; `--all` is required to
select every enabled configured model. Target validation completes before the first invocation.
Each result is committed to the registry as `verified` or a specific active failure, together with
its timestamp, duration, usage, and diagnostic; raw prompts and responses are not stored. A later
non-model-consuming refresh preserves that result while the configured provider/model/effort
identity remains unchanged.

Newly discovered but undeclared model IDs are recorded disabled rather than silently entering a
team candidate pool. Once the registry is initialized, its effective configured entries constrain
runtime profile routing. Without an initialized registry, enabled authored declarations remain
eligible for compatibility.

Team composition is bounded by authored policy. New projects default to `auto`, so omitting
`--team` asks the configured read-only architect to assess the task. Select a known template
explicitly to use a fixed team:

```bash
norn plan "update one documentation example"
norn plan "update one documentation example" --team lean
norn run "migrate authentication data safely" --team auto
norn run "use the fixed compatibility workflow" --team standard
```

Automatic selection, whether supplied by `--team auto` or `teams.default: auto`, invokes
`roles.team_architect` through the `team-architect-max` profile (Fable max, then Sol max by
default). For initial composition it receives only the task, bounded repository metadata, the
observed model inventory, and the configured templates. Its strict `TeamAssessment` cannot invent
roles, models, commands, tools, or dependencies. Norn applies a deterministic risk floor and
expands the answer into an immutable `TeamPlan`; invalid, low-confidence, or unavailable architect
output falls back to the configured deterministic template.

For automatic runs, a failed local, tester, or final quality decision is also a controlled
recomposition checkpoint. Norn may ask the same read-only architect to reassess the bounded
runtime findings and append a new immutable TeamPlan revision. Recomposition can only retain or
expand the current template, testers, and specialists; it never shrinks the active team. The
shipped `teams.max_recompositions: 1` bounds this extra call, may be set to `0`, and is validated
between `0` and `3`. Explicit fixed templates are never automatically recomposed.

Existing project files that explicitly contain `teams.default: standard` retain the fixed
compatibility workflow after upgrading. Change that setting to `auto`, or use `--team auto` for one
lifecycle, to opt into automatic selection. The offline rollout matrix at
`evals/team-composition.yaml` gates the shipped automatic default against right-sizing,
under-provisioning correction, over-provisioning detection, unavailable Architect models, and the
non-removable tester/writer safety floor.

The shipped templates are `lean`, `standard`, and `deep`. They vary the parallel tester and
risk-selected specialist set as well as policy-owned profile overrides: `lean` uses medium/low
profiles for small work, `standard` preserves the previous model/workflow mapping, and `deep`
raises core reasoning and implementation roles to the Fable/Sol max candidate profile. UI E2E,
local deterministic gates, final quality thresholds, serialized workspace writers, and all round
limits remain non-removable. Custom templates and overrides must reference configured roles and
profiles and remain within their specialist caps.

Round limits are configured under `limits` or overridden per invocation:

```bash
norn run "task" --max-rounds 5 --max-total-rounds 12 \
  --max-planner-retries 2 --max-final-replans 2
```

Known Python, Node, Rust, and Go gates are auto-detected, including common Node typecheck scripts.
All gates are executed once by Norn outside provider agents. Projects can define additional test,
lint, typecheck, and build commands as shell-free argv lists:

```yaml
gates:
  enabled: true
  auto_detect: true
  commands:
    - name: integration
      kind: test
      command: [python3, -m, pytest, tests/integration]
    - name: frontend-types
      kind: typecheck
      command: [npm, run, typecheck]
      container:
        image: node:22-bookworm
        workdir: /workspace/apps/web
        network: none
```

A `container` makes Norn construct `docker run` directly without a shell. It bind-mounts the
detached implementation worktree at `/workspace`, uses the current uid/gid where supported, and
defaults to no network. Set a reviewed Docker network name only when the gate requires services.
Pin the image by digest when reproducibility across machines matters. Direct shell-free commands
such as `[docker, compose, exec, ...]` remain available for project-owned Compose environments.
Any configured or auto-detected deterministic command that does not pass creates a high-severity
finding, so one failed lint/typecheck/build is blocking under the shipped quality policy.

### Verification plans and multiple services

`norn verify` separates repository inspection from execution:

```bash
norn verify plan --no-agent --project /path/to/repository
norn verify plan --project /path/to/repository
norn verify run --project /path/to/repository
norn verify export --project /path/to/repository
```

`verify plan` starts no service, gate, or browser. `--no-agent` guarantees deterministic
inspection only and consumes no model allowance. Norn inventories explicit configuration,
workspace package scripts, known Python/Rust/Go tooling, lockfiles, and E2E configuration, then
hashes the canonical input. Simple repositories resolve without another model call. Only an
incomplete or ambiguous plan invokes the read-only `verification_architect`, whose shipped
profile is `terra-medium`; it may select current candidate IDs and typed service references but
cannot author executable argv.

Resolution keeps explicit `gates`, `e2e`, and `verification` configuration authoritative, then
tries an exact-fingerprint accepted plan, unambiguous discovery, and finally the architect.
Unknown or stale candidates, unsafe or symlinked working directories, non-loopback bindings,
untyped interpolation, and dependency cycles fail before a process starts. `verify export` never
uses a model and writes a non-overwriting explicit proposal to
`.norn/verification-plan.yaml`; review and merge the proposal into project configuration rather
than treating it as an automatic migration from `init-e2e`.

Explicit multi-service configuration may authorize shell-free commands that discovery cannot
infer:

```yaml
verification:
  services:
    - id: api
      command: [python, -m, example_api]
      allowed_argv_append: [--port]
      ports: [{id: http, bind: 127.0.0.1, value: auto}]
      argv_append: [--port, {ref: service.api.port.http}]
      readiness:
        type: http
        url: {ref: service.api.url.http}
        path: /health
    - id: web
      command: [npm, run, dev]
      allowed_argv_append: [--, --host, --port, --strictPort, 127.0.0.1]
      depends_on: [api]
      ports: [{id: http, bind: 127.0.0.1, value: auto}]
      argv_append:
        [--, --host, 127.0.0.1, --port, {ref: service.web.port.http}, --strictPort]
      env:
        API_URL: {ref: service.api.url.http}
      readiness:
        type: http
        url: {ref: service.web.url.http}
  checks:
    - id: integration
      kind: test
      command: [python, -m, unittest, discover, -s, tests/integration, -v]
      depends_on: [api]
  e2e:
    command: [npm, run, test:e2e]
    base_url: {ref: service.web.url.http}
    depends_on: [web]
```

Norn reserves isolated loopback ports, starts services in dependency order, resolves only typed
argv/environment references, waits for HTTP or TCP readiness, runs checks sequentially, validates
E2E evidence, and stops managed process groups in reverse order on every outcome. External
services are readiness-checked but never stopped. `norn run` recomputes the inventory after each
implementation round and uses the same engine. At most one replacement plan may be attempted for
an infrastructure mismatch; a lint, test, build, browser assertion, or quality failure is a
product result and never triggers command selection again.

Each run records its immutable team-plan revision, role responses, model selections, attempts,
findings, test results, and round history below `.norn/runs/<run-id>/`. The shared
`.norn/runs/state.sqlite3` database is the authoritative run, event, team-plan, role-graph,
role-execution, provider-attempt, verification-plan, and verification-execution store; JSON and
Markdown files remain portable projections and evidence. The latest composition is projected to
`artifacts/team-plan.json`, with its
assessment in `artifacts/team-assessment.json`, invocation states in
`artifacts/role-executions.json`, and ordered fallback/retry state in
`artifacts/provider-attempts.json`. Verification revisions and exact resolved argv/log records
are projected to `artifacts/verification-plan*.json` and
`artifacts/verification-executions.json`. Portable run files are registered after writing with their
safe relative path, kind, media type, byte size, SHA-256 digest, timestamps, and owning role
execution when applicable. The rebuildable catalog is projected to
`artifacts/artifact-manifest.json`; registered JSON reads fail closed if the content no longer
matches its SQLite audit digest.
TeamPlan dependencies are validated as a DAG and selected read-only roles execute in stable
dependency-ready waves. Recovery marks invocations and provider attempts left running by a dead
process as interrupted; it never silently resumes a partial model call. Runs created by this
version persist a hashed request envelope for plain `read-only` invocations. After recovery, an
explicit
`norn continue <run-id> --execution <execution-id>` reissues that exact request as a fresh,
parent-linked invocation only when the envelope, recorded workspace path, and available Git
workspace fingerprint still match. Workspace-writing, TEST, danger-full-access, transformed, and
legacy invocations are never eligible. A continuation does not reopen the terminal run or infer
downstream pipeline work; its result is added to the escalation artifacts for human review.
Any non-successful stop also produces `escalation-report.md` and `escalation.json` for human
judgment.

By default, `run` requires a clean Git repository and implements inside a
detached `.norn/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
direct changes to the selected project directory are intentional.

Fresh Git worktrees contain tracked files only. Projects whose tests require
ignored dependencies, local environment files, or a prepared database can
provision the worktree before any provider role starts:

```yaml
git:
  worktree: true
  worktree_setup:
    - name: php-dependencies
      copy: laravel/vendor
    - name: node-dependencies
      copy: node_modules
      optional: true
    - name: test-database
      command: [scripts/setup-norn-database, --template]
      timeout_seconds: 900
```

Each step has exactly one action. `copy` materializes a project-relative path
from the source checkout; `command` is a shell-free argv list executed in the
new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
`NORN_RUN_ID`. A failed required step stops the run before model invocation.

## Development and packaging

Install the local development tools and run the same checks as CI:

```bash
python3 -m pip install --editable ".[dev]"
ruff format --check .
ruff check .
python3 -m unittest discover -v
python3 -m build
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
```

CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
built wheel in a clean virtual environment and verifies both Codex and Claude
skill copies. It uploads the distributions as a short-lived workflow artifact.
Publishing a GitHub Release runs the same deterministic gates before sending
the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
invokes subscription models.

Release acceptance is intentionally split in two: CI performs deterministic
package checks, while local acceptance invokes the authenticated subscription
CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
private vulnerability reporting.

The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
the pre-1.0 migration; new automation should use `norn`.

## License

Copyright 2026 uruca-kk. Licensed under the
[Apache License 2.0](LICENSE).
