nawabari 0.2.0 → 0.4.1

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 (50) hide show
  1. package/README.md +230 -15
  2. package/dist/cli.d.ts +5 -0
  3. package/dist/cli.js +704 -115
  4. package/dist/cli.js.map +1 -1
  5. package/dist/contract.js +135 -2
  6. package/dist/contract.js.map +1 -1
  7. package/dist/domain/errors.d.ts +1 -1
  8. package/dist/domain/errors.js +4 -1
  9. package/dist/domain/errors.js.map +1 -1
  10. package/dist/domain/sandbox-launcher.d.ts +35 -0
  11. package/dist/domain/sandbox-launcher.js +679 -0
  12. package/dist/domain/sandbox-launcher.js.map +1 -0
  13. package/dist/domain/sandbox.d.ts +190 -0
  14. package/dist/domain/sandbox.js +363 -0
  15. package/dist/domain/sandbox.js.map +1 -0
  16. package/dist/domain/session-backend.d.ts +5 -3
  17. package/dist/domain/session-backend.js +102 -4
  18. package/dist/domain/session-backend.js.map +1 -1
  19. package/dist/domain/session.d.ts +116 -2
  20. package/dist/domain/session.js +36 -0
  21. package/dist/domain/session.js.map +1 -1
  22. package/dist/errors.d.ts +1 -1
  23. package/dist/errors.js.map +1 -1
  24. package/dist/git.d.ts +62 -0
  25. package/dist/git.js +299 -0
  26. package/dist/git.js.map +1 -1
  27. package/dist/operation-authorization.d.ts +5 -2
  28. package/dist/operation-authorization.js +6 -7
  29. package/dist/operation-authorization.js.map +1 -1
  30. package/dist/output-budget.d.ts +16 -0
  31. package/dist/output-budget.js +58 -0
  32. package/dist/output-budget.js.map +1 -0
  33. package/dist/presentation.js +28 -4
  34. package/dist/presentation.js.map +1 -1
  35. package/dist/registry/atomic.d.ts +24 -0
  36. package/dist/registry/atomic.js +35 -2
  37. package/dist/registry/atomic.js.map +1 -1
  38. package/dist/registry/errors.d.ts +1 -1
  39. package/dist/registry/errors.js +1 -0
  40. package/dist/registry/errors.js.map +1 -1
  41. package/dist/repository-evidence.d.ts +74 -0
  42. package/dist/repository-evidence.js +7 -0
  43. package/dist/repository-evidence.js.map +1 -0
  44. package/dist/resource-claims.d.ts +25 -3
  45. package/dist/resource-claims.js +57 -5
  46. package/dist/resource-claims.js.map +1 -1
  47. package/dist/session-registry.d.ts +68 -7
  48. package/dist/session-registry.js +422 -149
  49. package/dist/session-registry.js.map +1 -1
  50. package/package.json +5 -3
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
@@ -64,6 +112,8 @@ The result schemas expose the following identities:
64
112
  | claims | `claim_id`, `session_id`, `resource`, `mode` |
65
113
  | authorization | `operation`, `allowed`, `code`, `claim_ids` |
66
114
  | checkpoint evidence | `head`, `changed`, `staged`, `unstaged`, `untracked`, `in_claim`, `out_of_claim` |
115
+ | repository evidence | `session_id`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash` |
116
+ | bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `patch`, `evidence_hash` |
67
117
  | commit/push | `commit_sha`, `remote`, `branch`, `target`, `relation` |
68
118
  | reconciliation/cleanup | `clean`, `issues`, `candidates`, `cleaned`, `blocked`, `recovery_hints` |
69
119
 
@@ -74,6 +124,35 @@ The local lifecycle requires Git and the repository-local registry/lock only;
74
124
  it does not require Mottainai, GitHub, `gh`, network access, an LLM, or a
75
125
  coding-agent runtime.
76
126
 
127
+ ## Read-only repository evidence
128
+
129
+ The evidence family is session-addressed and has no task, Issue, semantic, or
130
+ GitHub interpretation. It is the physical repository authority for one owned
131
+ session:
132
+
133
+ ```bash
134
+ git nawabari evidence snapshot --session "$NAWABARI_SESSION_ID" --json
135
+ git nawabari diff --session "$NAWABARI_SESSION_ID" --path src/example.ts --json
136
+ git nawabari diff --session "$NAWABARI_SESSION_ID" --path src/example.ts \
137
+ --patch --max-bytes 32768 --max-hunks 32 --json
138
+ ```
139
+
140
+ `evidence snapshot` verifies the registry's repository/worktree/branch owner,
141
+ then reuses checkpoint's exact NUL-safe Git observation for `changed`,
142
+ `staged`, `unstaged`, and `untracked` paths. It also reports canonical per-path
143
+ stats, `clean`, the current `head`, session state, and an `evidence_hash`.
144
+ New sessions persist the exact creation/base revision as `base_revision`;
145
+ legacy records that lack this field report `base_revision: null` and
146
+ `base_revision_proven: false` rather than inferring it from a mutable ref.
147
+
148
+ `diff` requires at least one explicit concrete path and never accepts a glob or
149
+ an empty repository-wide selection. Stats are returned by default. Patch text
150
+ requires `--patch` and is bounded to at most 64 paths, 64 KiB, and 128 hunks;
151
+ the caller may request smaller limits. Unrepresentable Git observations fail
152
+ with `GIT_STATE_AMBIGUOUS`; a requested path whose stat is not exposed by Git
153
+ remains in the result with `available: false` and makes snapshot evidence
154
+ `complete: false`, so no path silently disappears.
155
+
77
156
  ## Session lifecycle
78
157
 
79
158
  Session IDs are generated automatically as UUIDv7 values. They are immutable
@@ -91,6 +170,11 @@ git nawabari gc --dry-run --json
91
170
  git nawabari doctor --json
92
171
  ```
93
172
 
173
+ `status --json` reports the resolved `managed_worktree_root` used when
174
+ `session create` omits `--worktree`. `session create --help --json` describes
175
+ all four create options as optional and reports defaults for branch, worktree,
176
+ base (`HEAD`), and label.
177
+
94
178
  `session create` provisions a dedicated worktree and mutable branch atomically
95
179
  under the repository-scoped mutation lock. The default/integration worktree
96
180
  and its protected branch cannot be session resources. `session id` and the
@@ -108,6 +192,19 @@ every candidate that is not safe. Cleanup revalidates the physical worktree,
108
192
  branch, and `HEAD` observations immediately before each destructive Git
109
193
  operation.
110
194
 
195
+ Routine `session list` and `status` output excludes `closed` history and is
196
+ limited to 64 records. Use `--all` (or `--history`) for an explicit complete
197
+ history view; closed records remain persisted and are never silently deleted
198
+ by listing or cleanup.
199
+
200
+ `gc` stale eligibility is separate from closed-history retention. Its default
201
+ threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`;
202
+ records already in `stale` or `closing` state are eligible, and an otherwise
203
+ live record is also eligible when Git reports its registered worktree as
204
+ missing or prunable. Physical Git/worktree state is authoritative for that
205
+ check. `gc --dry-run` and `gc --apply` do not treat a closed record as a stale
206
+ cleanup candidate.
207
+
111
208
  `doctor` includes a non-destructive `reconciliation` check. It reports
112
209
  registry/Git ownership drift, including missing or prunable worktrees and
113
210
  unregistered physical worktrees, without repairing or deleting anything.
@@ -119,8 +216,10 @@ same repository registry and protected by the same mutation lock. They are
119
216
  caller declarations; Nawabari does not infer them from task text or source
120
217
  code. Claim JSON exposes `schema_version`, `claim_id`, `session_id`, the
121
218
  repository/worktree identities, canonical `resource`, `mode`, and timestamps.
122
- The initial claim schema version is `1` and supports `read`, `write`, and
123
- `exclusive-write`.
219
+ The claim schema version is `2` and supports `read`, `write`, and
220
+ `exclusive-write`. Schema v1 records use different overlap semantics and are
221
+ not interpreted implicitly: an embedding caller must explicitly run
222
+ `SessionRegistry.migrate()` before using them.
124
223
 
125
224
  ```bash
126
225
  git nawabari session claim --session "$NAWABARI_SESSION_ID" \
@@ -131,14 +230,45 @@ git nawabari session update --session "$NAWABARI_SESSION_ID" \
131
230
  git nawabari session release --session "$NAWABARI_SESSION_ID" --json
132
231
  ```
133
232
 
233
+ `session update` (and its `resource update` alias) atomically replaces a
234
+ session's _complete_ claim set in one `updateClaims()` transaction, backed by
235
+ the same repository lock as every other mutation. `--resource`/`--mode` are
236
+ repeatable to submit a multi-resource desired set in a single call; each
237
+ `--resource` must be immediately followed by its own `--mode`, so pairing is
238
+ positional adjacency rather than flag order and repeated resources can never
239
+ be associated with the wrong mode:
240
+
241
+ ```bash
242
+ git nawabari session update --session "$NAWABARI_SESSION_ID" \
243
+ --resource src/a.ts --mode exclusive-write \
244
+ --resource src/b.ts --mode exclusive-write \
245
+ --json
246
+ ```
247
+
248
+ If any requested claim in the set is invalid or conflicts, the whole update
249
+ is rejected and the session's prior claim set is left unchanged; no partial
250
+ or empty intermediate claim state is ever observable. Submitting the same
251
+ complete desired set again is idempotent. A successful replacement's JSON
252
+ exposes the resulting `claims` together with machine-readable `added` and
253
+ `released` claims.
254
+
255
+ The modes have these normative meanings:
256
+
257
+ - `read`: a non-mutating access declaration. It is not a consistency lease,
258
+ so it may overlap an ordinary `write` claim.
259
+ - `write`: ordinary source-modification authority. It may overlap `read`, but
260
+ not another writer or any `exclusive-write` claim.
261
+ - `exclusive-write`: stronger ownership-sensitive mutation authority. It
262
+ excludes every overlapping claim, including `read`.
263
+
134
264
  Overlapping claims use this complete compatibility matrix; non-overlapping
135
265
  claims are compatible for every mode:
136
266
 
137
- | existing \/ requested | read | write | exclusive-write |
138
- | --------------------- | ---------- | -------- | --------------- |
139
- | read | compatible | conflict | conflict |
140
- | write | conflict | conflict | conflict |
141
- | exclusive-write | conflict | conflict | conflict |
267
+ | existing \/ requested | read | write | exclusive-write |
268
+ | --------------------- | ---------- | ---------- | --------------- |
269
+ | read | compatible | compatible | conflict |
270
+ | write | compatible | conflict | conflict |
271
+ | exclusive-write | conflict | conflict | conflict |
142
272
 
143
273
  Claims use canonical repository-relative POSIX paths. Literal path segments,
144
274
  `*`/`?` segment wildcards, and a complete `**` segment are supported. Empty,
@@ -149,6 +279,26 @@ garbage-collecting a session releases its claims; no separate claim registry
149
279
  or claim lock exists. Claims describe ownership state only and do not provide
150
280
  OS-level filesystem observation or a filesystem sandbox.
151
281
 
282
+ An ordinary source change uses `write` and can proceed while another session
283
+ holds a `read` declaration:
284
+
285
+ ```bash
286
+ git nawabari session claim --session "$NAWABARI_SESSION_ID" \
287
+ --resource src/example.ts --mode write --json
288
+ git nawabari authorize --session "$NAWABARI_SESSION_ID" \
289
+ --operation source-write --resource src/example.ts --json
290
+ ```
291
+
292
+ A stronger ownership-sensitive operation uses `exclusive-write` and therefore
293
+ requires no overlapping claim:
294
+
295
+ ```bash
296
+ git nawabari session claim --session "$NAWABARI_SESSION_ID" \
297
+ --resource src/example.ts --mode exclusive-write --json
298
+ git nawabari authorize --session "$NAWABARI_SESSION_ID" \
299
+ --operation commit --resource src/example.ts --json
300
+ ```
301
+
152
302
  ## Ownership guard
153
303
 
154
304
  `git nawabari guard` is a cheap, side-effect-free authorization decision for a
@@ -187,6 +337,17 @@ worktree=$(printf '%s' "$created" | jq -r .worktree)
187
337
  git nawabari session close --session "$session_id" --json
188
338
  ```
189
339
 
340
+ For a worker that must receive the protected filesystem/process boundary, use
341
+ the managed launcher instead of starting the worker directly:
342
+
343
+ ```bash
344
+ git nawabari session run --session "$session_id" -- node worker.js
345
+ ```
346
+
347
+ The launcher owns only the child sandbox topology and process attachment. The
348
+ session registry, worktree/branch ownership, resource claims and lifecycle
349
+ remain the authoritative Nawabari domain state.
350
+
190
351
  The orchestrator owns scheduling, prompts, and worker lifetime; Nawabari owns
191
352
  only local session identity, worktree/branch ownership, and safe cleanup. No
192
353
  Mottainai, GitHub, `gh`, network, or agent-runtime dependency is required.
@@ -217,9 +378,15 @@ repository-relative resources. Nawabari independently verifies the current
217
378
  repository, owned worktree, branch, active session, and persisted claims;
218
379
  caller-supplied labels do not weaken that decision. The JSON result is the
219
380
  automation contract and reports stable allow/deny codes such as
220
- `MISSING_RESOURCE_CLAIM`, `RESOURCE_CLAIM_CONFLICT`, `INVALID_RESOURCE`, and
381
+ `MISSING_RESOURCE_CLAIM`, `INSUFFICIENT_CLAIM_MODE`, `RESOURCE_CLAIM_CONFLICT`,
382
+ `INVALID_RESOURCE`, and
221
383
  the existing ownership/physical-observation codes.
222
384
 
385
+ `INSUFFICIENT_CLAIM_MODE` means a matching claim exists but its granted mode
386
+ is weaker than the operation requires. Its bounded details identify the
387
+ resource, required access, and matching granted mode names;
388
+ `MISSING_RESOURCE_CLAIM` remains reserved for an absent matching claim.
389
+
223
390
  **`authorize` returns an authorization decision only; it does NOT execute the
224
391
  operation itself.** Governed commit and push execution use this same decision
225
392
  path before invoking bounded Git subprocesses.
@@ -234,17 +401,51 @@ repository-relative resources. Every resource must be covered by an active
234
401
  `exclusive-write` claim; all Git-visible changed/staged paths must be in the
235
402
  explicit list. JSON includes the resulting `commit_sha`.
236
403
 
404
+ Commit evidence distinguishes three sets: the caller's **declared/authorized**
405
+ resources (the explicit, claim-covered list a caller passed in), the
406
+ **staged** set Git reports as staged immediately before the commit runs, and
407
+ the **actual committed** set — read back from the resulting commit itself via
408
+ a bounded, NUL-safe `git diff-tree` observation, not inferred from staging
409
+ intent. The `resources` field in a successful `commit --json` result is
410
+ always the actual committed set, proven equal to or a subset of the
411
+ authorized resources; if Git staging/index drift between staging and the
412
+ commit (a hook, a concurrent process) causes the actual commit to contain a
413
+ path outside the authorized set, the result is not reported as an ordinary
414
+ successful commit — it fails with `COMMIT_RESULT_DIVERGED`, which retains the
415
+ resulting `commitSha` (the Git commit already happened) alongside the
416
+ authorized, actual, and divergent path sets for recovery/reconciliation.
417
+
237
418
  ```bash
238
419
  git nawabari commit --session "$NAWABARI_SESSION_ID" \
239
420
  --message 'record the local change' --resource src/example.ts --json
240
421
  ```
241
422
 
423
+ An optional `--message-pattern <regex>` validates the final message against a
424
+ caller-declared rule before Git is invoked; Nawabari does not own or infer
425
+ commit-message conventions (such as Conventional Commits) itself, so this
426
+ check runs only when a caller explicitly supplies a pattern, and a mismatch
427
+ fails with `INVALID_COMMIT_MESSAGE` before anything is staged. The pattern is
428
+ bounded to 512 characters and is evaluated before the repository lock is
429
+ acquired, so a pathological caller-supplied pattern cannot stall other
430
+ sessions' governed operations. A repository's own `commit-msg` Git hook (if
431
+ any) still runs normally, since governed commit invokes real `git commit`.
432
+
433
+ ```bash
434
+ git nawabari commit --session "$NAWABARI_SESSION_ID" \
435
+ --message 'feat: record the local change' --resource src/example.ts \
436
+ --message-pattern '^(feat|fix|docs|refactor|test|chore): .+$' --json
437
+ ```
438
+
242
439
  Governed push requires explicit claim-covered resources and an explicit
243
440
  `--remote`/`--branch` target. Existing upstream and local/remote relation are
244
441
  inspected before mutation. A missing upstream requires `--create-upstream`;
245
- behind or diverged history requires explicit `--force`, which uses
246
- `--force-with-lease`. The JSON result identifies the pushed `target` and
247
- reports its relation.
442
+ behind or diverged history requires explicit `--force`. Force pushes use an
443
+ exact `--force-with-lease` bound to the observed remote branch SHA; non-force
444
+ pushes rely on `--no-force` without any lease option. Nawabari fetches only
445
+ the explicit remote branch into a disposable ref when local ancestry is
446
+ missing; it does not update tracking refs or fetch unrelated branches/tags.
447
+ The JSON result includes the immutable `source_sha`, explicit `target_ref`,
448
+ observed `observed_remote_sha`, and relation.
248
449
 
249
450
  ```bash
250
451
  git nawabari push --session "$NAWABARI_SESSION_ID" \
@@ -259,6 +460,15 @@ filesystem activity that is reverted, ignored, or otherwise not observable in
259
460
  the Git checkpoint is outside Nawabari's guarantee; this feature is not an
260
461
  OS-level filesystem monitor.
261
462
 
463
+ Checkpoint canonicalization fails closed: a Git-reported path that cannot be
464
+ represented as a canonical repository resource (traversal, symlink escape, or
465
+ syntax reserved for the claim/glob model) never disappears from evidence.
466
+ Checkpoint fails the whole observation with `GIT_STATE_AMBIGUOUS` instead of
467
+ silently omitting the path, so a caller can never mistake an unrepresentable
468
+ observation for a clean one. This mirrors the strictness governed mutation
469
+ already applies to the same Git-observed paths, so checkpoint evidence is
470
+ never weaker than mutation authorization.
471
+
262
472
  ## Physical execution context
263
473
 
264
474
  Nawabari treats Git and the canonical filesystem as the authority for every
@@ -280,6 +490,11 @@ worktree paths, and existing local branches are rejected deterministically;
280
490
  the repository lock serializes Nawabari provisioning and Git's own ref checks
281
491
  remain the final collision authority.
282
492
 
493
+ An explicit `--base` ref that is empty, malformed, or does not resolve to a
494
+ commit fails with `INVALID_BASE_REF`. The bounded JSON details retain the
495
+ rejected ref, identify `HEAD` as the default recovery base, and include the
496
+ retry hint to omit `--base`; Nawabari does not enumerate or fuzzy-search refs.
497
+
283
498
  ## Repository state and concurrency
284
499
 
285
500
  The authoritative registry is stored in the repository-common Git directory at
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>;