nawabari 0.3.0 → 0.5.0

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 CHANGED
@@ -5,10 +5,51 @@ agents. It gives each active session one exclusively owned worktree and one
5
5
  mutable branch. It works without GitHub, `gh`, a network connection, Mottainai,
6
6
  or a particular agent runtime.
7
7
 
8
- Nawabari governs operations routed through Nawabari. It is an authorization and
9
- ownership boundary, not an operating-system or filesystem sandbox: a process
10
- that already has filesystem permissions can still edit another worktree
11
- directly.
8
+ Nawabari governs operations routed through Nawabari. By default it is an
9
+ authorization and ownership boundary, not an operating-system or filesystem
10
+ sandbox: a process that already has filesystem permissions can still edit
11
+ another worktree directly. This default (legacy) mode is unchanged.
12
+
13
+ The opt-in protected session mode defines a Linux-only OS/filesystem/process
14
+ enforcement boundary underneath the existing session/worktree/resource
15
+ authority (`src/domain/sandbox.ts`, contract
16
+ `nawabari.sandbox-execution.v1`). It binds one existing Nawabari session
17
+ resolved through the authoritative registry/guard path to a typed sandbox
18
+ execution request; it does not create a second session identity. A
19
+ machine-readable capability/doctor report distinguishes required Linux
20
+ primitives (bubblewrap, user/mount/PID/IPC/UTS namespaces) from optional
21
+ defense-in-depth primitives (cgroups v2, Landlock, seccomp, capability
22
+ inspection). When protected execution is requested and a required capability
23
+ is unavailable or the platform is unsupported, resolution fails closed and
24
+ never returns a request that claims the legacy unsandboxed path is
25
+ protected. The lower-level contract remains responsible only for capability
26
+ detection and the typed request/result shape; `resolveSandboxExecutionRequest()`
27
+ returns a request without invoking bubblewrap. The managed `session run`
28
+ launcher consumes that request and establishes the protected boundary. Network
29
+ mode is honestly reported as `inherited` (shared with the host), not isolated.
30
+
31
+ `git nawabari session run --session <id> -- <command> [args...]` is the
32
+ managed protected execution entry point. It resolves the existing session
33
+ through the normal guard authority and compiles a fixed bubblewrap argv; the
34
+ command is passed after an argv terminator and is never interpreted by a
35
+ shell. Resolution or launch failure never falls back to the legacy ambient
36
+ filesystem view.
37
+
38
+ The canonical profile starts from a private root, mounts only the owned
39
+ worktree read-write, gives each session private `/tmp`, `/proc`, HOME and
40
+ cache state, and exposes no sibling worktree or control-plane path. A small
41
+ repository-owned `nawabari/sandbox/shared-home` subtree is the only HOME state
42
+ shared between sessions. Selected host user-tool directories (`~/.local/bin`
43
+ and pnpm's user bin when present) are read-only; credentials and the rest of
44
+ the host HOME are not mounted. `/dev`, system certificates/configuration, and
45
+ the detected runtime are explicit read-only/runtime inputs.
46
+
47
+ On standalone Linux the profile uses existing `/usr`, `/bin`, `/lib*` and
48
+ selected `/etc` paths only when present. On NixOS it additionally selects
49
+ `/nix/store`, `/run/current-system`, `/run/wrappers`, and the per-user profile
50
+ when present; no `/usr` layout is assumed. Missing required paths or namespace
51
+ support produces a stable capability/topology error. Network remains
52
+ inherited by design.
12
53
 
13
54
  ## Install
14
55
 
@@ -41,6 +82,13 @@ result-schema versions, identity fields, and stable `failure_codes`. The
41
82
  package version is release metadata; it is not a substitute for the
42
83
  machine-contract identifier.
43
84
 
85
+ The `resource-claims` capability additionally exposes a machine-readable
86
+ `claim_set_replacement` object (`commands`, `atomic: true`,
87
+ `pairing: "adjacent-resource-mode"`, `idempotent_retry: true`,
88
+ `unchanged_on_rejection: true`) describing the atomic multi-claim replacement
89
+ surface documented above, so a caller can discover this contract instead of
90
+ assuming it from the CLI help text.
91
+
44
92
  The supported standalone sequence is:
45
93
 
46
94
  ```text
@@ -123,9 +171,15 @@ git nawabari doctor --json
123
171
  ```
124
172
 
125
173
  `status --json` reports the resolved `managed_worktree_root` used when
126
- `session create` omits `--worktree`. `session create --help --json` describes
127
- all four create options as optional and reports defaults for branch, worktree,
128
- base (`HEAD`), and label.
174
+ `session create` omits `--worktree` and `--worktree-root`. `session create
175
+ --help --json` describes all create options as optional and reports defaults
176
+ for branch, worktree, worktree root, base (`HEAD`), and label.
177
+
178
+ `--worktree-root` selects only the parent directory for a new session
179
+ worktree; Nawabari still derives the final worktree basename from its own
180
+ session-naming contract. It is mutually exclusive with `--worktree`, the
181
+ exact-path override. Every session record's `worktree_root` field reports
182
+ the resolved parent of that session's worktree.
129
183
 
130
184
  `session create` provisions a dedicated worktree and mutable branch atomically
131
185
  under the repository-scoped mutation lock. The default/integration worktree
@@ -182,6 +236,28 @@ git nawabari session update --session "$NAWABARI_SESSION_ID" \
182
236
  git nawabari session release --session "$NAWABARI_SESSION_ID" --json
183
237
  ```
184
238
 
239
+ `session update` (and its `resource update` alias) atomically replaces a
240
+ session's _complete_ claim set in one `updateClaims()` transaction, backed by
241
+ the same repository lock as every other mutation. `--resource`/`--mode` are
242
+ repeatable to submit a multi-resource desired set in a single call; each
243
+ `--resource` must be immediately followed by its own `--mode`, so pairing is
244
+ positional adjacency rather than flag order and repeated resources can never
245
+ be associated with the wrong mode:
246
+
247
+ ```bash
248
+ git nawabari session update --session "$NAWABARI_SESSION_ID" \
249
+ --resource src/a.ts --mode exclusive-write \
250
+ --resource src/b.ts --mode exclusive-write \
251
+ --json
252
+ ```
253
+
254
+ If any requested claim in the set is invalid or conflicts, the whole update
255
+ is rejected and the session's prior claim set is left unchanged; no partial
256
+ or empty intermediate claim state is ever observable. Submitting the same
257
+ complete desired set again is idempotent. A successful replacement's JSON
258
+ exposes the resulting `claims` together with machine-readable `added` and
259
+ `released` claims.
260
+
185
261
  The modes have these normative meanings:
186
262
 
187
263
  - `read`: a non-mutating access declaration. It is not a consistency lease,
@@ -251,6 +327,48 @@ Detached, corrupt, missing, or conflicting state fails closed. The guard does
251
327
  not install hooks and does not prevent direct filesystem writes outside
252
328
  Nawabari.
253
329
 
330
+ ## Actionable lifecycle rejections
331
+
332
+ Stable codes are necessary but not sufficient for a caller to pick a safe
333
+ next step without a second, separate inspection. For the codes below,
334
+ Nawabari's `details` carry bounded, deterministic evidence and a
335
+ `safeActions`/`safe_actions` list of stable, kebab-case next-action
336
+ identifiers, in addition to the human-readable `message`. JSON and human
337
+ output always render the identical underlying result; only the formatting
338
+ differs.
339
+
340
+ - **`RESOURCE_CLAIM_CONFLICT`** (`session claim`/`session update`,
341
+ `authorize`, `guard --operation`) reports the blocking claim
342
+ (`ownerClaimId`, `ownerResource`, `ownerMode`) and the blocking session's
343
+ canonical identity (`ownerSessionId`, `ownerWorktree`, `ownerBranch`, and
344
+ `ownerLabel` when the session has one) in the same result, so a caller
345
+ never needs a second `session list` scan to find the owner.
346
+ - **`PROTECTED_WORKTREE`**/**`PROTECTED_BRANCH`** raised by a live command
347
+ (not by cleanup) add `phase: "execution"` and distinguish the current
348
+ (protected) execution context from the referenced `--session` target:
349
+ `requestedSessionId`, `targetWorktree`, `targetBranch`, and `targetState`
350
+ when that session exists. `safeActions` names the deterministic fix
351
+ (`run-from-managed-session-worktree`, `select-target-session-explicitly`)
352
+ instead of the unrelated cleanup-time hint.
353
+ - **`INVALID_SESSION_ID`** stays machine-ID based: an invalid `--session`
354
+ value is never silently reinterpreted as a label. When it exactly and
355
+ unambiguously matches one active session's label, the result adds
356
+ `session_id_hint` (the canonical session ID) and
357
+ `session_label_match: "unique"` as a non-authoritative hint. An ambiguous
358
+ or absent label match never guesses: `session_label_match` reports
359
+ `"ambiguous"` (with `session_label_match_count`) or `"none"` instead.
360
+ - **`RECOVERABLE_COMMITS`** raised by `session close` carries the same
361
+ `close_readiness`/`result_state` classification `session inspect` reports
362
+ for the identical state — `external_evidence_required` when ancestry alone
363
+ could not prove the branch safe and a `--integrated-revision` proof might
364
+ resolve it (e.g. after a squash/rebase merge), versus `blocked` when
365
+ supplied evidence failed to prove equivalence, versus `ambiguous` when Git
366
+ observation itself was inconclusive. Both surfaces reuse one authority, so
367
+ a raw close rejection and `session inspect` never drift apart.
368
+
369
+ None of the above weakens fail-closed behavior, changes an error code's
370
+ meaning, or performs any mutation while producing the rejection.
371
+
254
372
  ## Orchestrator integration
255
373
 
256
374
  An external orchestrator can create a session, capture the returned
@@ -267,6 +385,17 @@ worktree=$(printf '%s' "$created" | jq -r .worktree)
267
385
  git nawabari session close --session "$session_id" --json
268
386
  ```
269
387
 
388
+ For a worker that must receive the protected filesystem/process boundary, use
389
+ the managed launcher instead of starting the worker directly:
390
+
391
+ ```bash
392
+ git nawabari session run --session "$session_id" -- node worker.js
393
+ ```
394
+
395
+ The launcher owns only the child sandbox topology and process attachment. The
396
+ session registry, worktree/branch ownership, resource claims and lifecycle
397
+ remain the authoritative Nawabari domain state.
398
+
270
399
  The orchestrator owns scheduling, prompts, and worker lifetime; Nawabari owns
271
400
  only local session identity, worktree/branch ownership, and safe cleanup. No
272
401
  Mottainai, GitHub, `gh`, network, or agent-runtime dependency is required.
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,14 @@
1
+ import { type DomainResult } from "./domain/errors.js";
1
2
  import { type SessionBackend } from "./domain/session.js";
2
3
  import { type CliIO } from "./presentation.js";
4
+ import { type SandboxCommand, type SandboxExecutionResult, type SandboxProbe, type SandboxRuntimeLayout } from "./domain/sandbox.js";
3
5
  export type CliDependencies = {
4
6
  backend?: SessionBackend;
5
7
  cwd?: string;
6
8
  io?: CliIO;
7
9
  version?: string;
10
+ sandboxRunner?: (request: import("./domain/sandbox.js").SandboxExecutionRequest, command: SandboxCommand) => Promise<DomainResult<SandboxExecutionResult>>;
11
+ sandboxProbe?: SandboxProbe;
12
+ sandboxRuntimeLayout?: SandboxRuntimeLayout;
8
13
  };
9
14
  export declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<number>;