nawabari 0.7.0 → 0.8.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.
Files changed (65) hide show
  1. package/README.md +262 -32
  2. package/dist/cli-command-registry.d.ts +44 -0
  3. package/dist/cli-command-registry.js +488 -0
  4. package/dist/cli-command-registry.js.map +1 -0
  5. package/dist/cli.d.ts +18 -0
  6. package/dist/cli.js +345 -584
  7. package/dist/cli.js.map +1 -1
  8. package/dist/contract.d.ts +16 -0
  9. package/dist/contract.js +505 -220
  10. package/dist/contract.js.map +1 -1
  11. package/dist/domain/cgroups-v2.d.ts +77 -0
  12. package/dist/domain/cgroups-v2.js +434 -0
  13. package/dist/domain/cgroups-v2.js.map +1 -0
  14. package/dist/domain/doctor.d.ts +4 -1
  15. package/dist/domain/doctor.js +20 -13
  16. package/dist/domain/doctor.js.map +1 -1
  17. package/dist/domain/errors.d.ts +7 -1
  18. package/dist/domain/errors.js +45 -0
  19. package/dist/domain/errors.js.map +1 -1
  20. package/dist/domain/landlock.d.ts +55 -0
  21. package/dist/domain/landlock.js +279 -0
  22. package/dist/domain/landlock.js.map +1 -0
  23. package/dist/domain/runtime.d.ts +12 -0
  24. package/dist/domain/runtime.js +50 -0
  25. package/dist/domain/runtime.js.map +1 -0
  26. package/dist/domain/sandbox-launcher.d.ts +26 -0
  27. package/dist/domain/sandbox-launcher.js +312 -23
  28. package/dist/domain/sandbox-launcher.js.map +1 -1
  29. package/dist/domain/sandbox-seccomp.d.ts +46 -0
  30. package/dist/domain/sandbox-seccomp.js +302 -0
  31. package/dist/domain/sandbox-seccomp.js.map +1 -0
  32. package/dist/domain/sandbox.d.ts +45 -1
  33. package/dist/domain/sandbox.js +176 -25
  34. package/dist/domain/sandbox.js.map +1 -1
  35. package/dist/domain/session-backend.d.ts +3 -1
  36. package/dist/domain/session-backend.js +155 -1
  37. package/dist/domain/session-backend.js.map +1 -1
  38. package/dist/domain/session.d.ts +128 -1
  39. package/dist/domain/session.js.map +1 -1
  40. package/dist/errors.d.ts +10 -1
  41. package/dist/errors.js.map +1 -1
  42. package/dist/failure-code-vocabulary.d.ts +20 -0
  43. package/dist/failure-code-vocabulary.js +216 -0
  44. package/dist/failure-code-vocabulary.js.map +1 -0
  45. package/dist/git.js +92 -6
  46. package/dist/git.js.map +1 -1
  47. package/dist/operation-authorization.d.ts +3 -3
  48. package/dist/registry/lock.d.ts +8 -0
  49. package/dist/registry/lock.js +38 -20
  50. package/dist/registry/lock.js.map +1 -1
  51. package/dist/repository-evidence.d.ts +16 -0
  52. package/dist/repository-evidence.js.map +1 -1
  53. package/dist/resource-claims.d.ts +17 -2
  54. package/dist/resource-claims.js +98 -27
  55. package/dist/resource-claims.js.map +1 -1
  56. package/dist/session-lifecycle-actions.d.ts +63 -0
  57. package/dist/session-lifecycle-actions.js +108 -0
  58. package/dist/session-lifecycle-actions.js.map +1 -0
  59. package/dist/session-lifecycle-classification.d.ts +67 -0
  60. package/dist/session-lifecycle-classification.js +355 -0
  61. package/dist/session-lifecycle-classification.js.map +1 -0
  62. package/dist/session-registry.d.ts +133 -3
  63. package/dist/session-registry.js +984 -91
  64. package/dist/session-registry.js.map +1 -1
  65. package/package.json +25 -25
package/README.md CHANGED
@@ -17,9 +17,11 @@ authority (`src/domain/sandbox.ts`, contract
17
17
  resolved through the authoritative registry/guard path to a typed sandbox
18
18
  execution request; it does not create a second session identity. A
19
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
20
+ primitives (bubblewrap, user/mount/PID/IPC/UTS namespaces, the versioned
21
+ seccomp baseline, and capability reduction) from optional defense-in-depth
22
+ primitives (cgroups v2 and Landlock). The sandbox report exposes Landlock's
23
+ observed ABI, support, and effective state (`available`, `enforced`,
24
+ `reduced-defense`, `incompatible`, or `error`). When protected execution is requested and a required capability
23
25
  is unavailable or the platform is unsupported, resolution fails closed and
24
26
  never returns a request that claims the legacy unsandboxed path is
25
27
  protected. The lower-level contract remains responsible only for capability
@@ -44,15 +46,43 @@ and pnpm's user bin when present) are read-only; credentials and the rest of
44
46
  the host HOME are not mounted. `/dev`, system certificates/configuration, and
45
47
  the detected runtime are explicit read-only/runtime inputs.
46
48
 
49
+ When the host exposes a compatible Landlock ABI, the protected launcher applies
50
+ a rule set derived from this same topology inside bubblewrap. An unavailable
51
+ or incompatible optional ABI leaves bubblewrap active and reports
52
+ `reduced-defense`; an adapter setup failure fails closed, and a profile that
53
+ explicitly requires Landlock also fails closed when its adapter cannot be
54
+ established. Setup diagnostics are bounded and no ambient/unsandboxed retry is
55
+ attempted.
56
+
47
57
  On standalone Linux the profile uses existing `/usr`, `/bin`, `/lib*` and
48
58
  selected `/etc` paths only when present. On NixOS it additionally selects
49
59
  `/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.
60
+ when present; broad FHS views (`/usr`, `/bin`, `/lib*`) are not selected when
61
+ the NixOS closure roots are present. Missing required paths or namespace
62
+ support produces a stable capability/topology error. The protected child uses
63
+ `nawabari.seccomp.v1`, a compatibility-first deny-list whose policy denials
64
+ return `EPERM` rather than hanging or terminating ordinary development
65
+ subprocess trees. Ambient capabilities are empty (`--cap-drop ALL`). Network
66
+ remains inherited by design.
67
+
68
+ The canonical Mottainai NixOS Runtime fixture can run the opt-in compatibility
69
+ conformance matrix as its unprivileged repository principal:
70
+
71
+ ```bash
72
+ NAWABARI_NIXOS_RUNTIME_CONFORMANCE=1 \
73
+ node --test --import tsx src/domain/nixos-runtime-compat.test.ts
74
+ ```
75
+
76
+ This invokes the normal `session run` CLI route and production launcher. It
77
+ checks explicit NixOS closure paths, private versus repository-shared HOME
78
+ state, Git/checkpoint authority, representative toolchain subprocesses, and
79
+ sequential/concurrent session isolation. It does not create a test-only
80
+ sandbox or treat Mottainai state as Nawabari authority.
53
81
 
54
82
  ## Install
55
83
 
84
+ Nawabari requires Node.js >=24.
85
+
56
86
  ```bash
57
87
  npm install -g nawabari
58
88
  ```
@@ -82,6 +112,17 @@ result-schema versions, identity fields, and stable `failure_codes`. The
82
112
  package version is release metadata; it is not a substitute for the
83
113
  machine-contract identifier.
84
114
 
115
+ The `protected-execution` capability separately advertises the versioned
116
+ `nawabari.sandbox-execution.v1` contract, its required and optional host
117
+ capabilities, the canonical `session run` entry point and `session exec` alias,
118
+ `network_mode: "inherited"`, and fail-closed behavior. Use
119
+ `nawabari doctor --json` to inspect the current host without creating or
120
+ selecting a session. Its `sandbox` report is produced by the same
121
+ `sandboxDoctorReport` authority used when resolving protected execution and
122
+ contains `platform_supported`, per-capability status, `ready`, and
123
+ `missing_required`. An unavailable required capability never implies an
124
+ ambient protected fallback.
125
+
85
126
  The `resource-claims` capability additionally exposes a machine-readable
86
127
  `claim_set_replacement` object (`commands`, `atomic: true`,
87
128
  `pairing: "adjacent-resource-mode"`, `idempotent_retry: true`,
@@ -89,6 +130,18 @@ The `resource-claims` capability additionally exposes a machine-readable
89
130
  surface documented above, so a caller can discover this contract instead of
90
131
  assuming it from the CLI help text.
91
132
 
133
+ Resource-claim semantics are generation `nawabari.resource-claims.v2` with
134
+ claim-record schema `2`. The standalone envelope deliberately remains
135
+ `nawabari.standalone-execution.v1`: this is a meaning-compatible top-level
136
+ identity, while callers select the child resource-claim generation before
137
+ operating. A future meaning-changing claim authorization, conflict, transition,
138
+ release, or required-mode change must publish a new resource-claim generation
139
+ and identity; the package version alone is never a compatibility decision.
140
+ The capability binds every lifecycle command and alias to its result schema,
141
+ implementation-owned stable failure vocabulary, transition-matrix identity,
142
+ CAS/force and rejected-non-mutation guarantees, and deterministic recovery
143
+ action schema.
144
+
92
145
  The supported standalone sequence is:
93
146
 
94
147
  ```text
@@ -111,11 +164,11 @@ The result schemas expose the following identities:
111
164
  | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
112
165
  | session lifecycle | `session_id`, `repository`, `worktree`, `branch`, `state` |
113
166
  | explicit discard | `previous_head`, `worktree_removed`, `branch_removed`, `released_claims`, `final_state`, final `session.state`/`terminal_operation` |
114
- | claims | `claim_id`, `session_id`, `resource`, `mode` |
167
+ | claims | `claim_id`, `session_id`, `resource`, `mode`, `claim_set_generation`, `previous_claim_set_generation` |
115
168
  | authorization | `operation`, `allowed`, `code`, `claim_ids` |
116
169
  | checkpoint evidence | `head`, `changed`, `staged`, `unstaged`, `untracked`, `in_claim`, `out_of_claim` |
117
- | repository evidence | `session_id`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash` |
118
- | bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `patch`, `evidence_hash` |
170
+ | repository evidence | `session_id`, `session_updated_at`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash` |
171
+ | bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `diagnostics`, `patch`, `evidence_hash` |
119
172
  | commit/push | `commit_sha`, `remote`, `branch`, `target`, `relation` |
120
173
  | reconciliation/cleanup | `clean`, `issues`, `candidates`, `cleaned`, `blocked`, `recovery_hints` |
121
174
 
@@ -147,13 +200,24 @@ New sessions persist the exact creation/base revision as `base_revision`;
147
200
  legacy records that lack this field report `base_revision: null` and
148
201
  `base_revision_proven: false` rather than inferring it from a mutable ref.
149
202
 
203
+ `session_updated_at` is the UTC timestamp of the last authoritative
204
+ session-state mutation, not a general Git filesystem mtime. A successful
205
+ Nawabari-managed `commit` is such a mutation, so the registry timestamp is
206
+ persisted before the command returns and a subsequent snapshot reports it with
207
+ the resulting `head`. Git changes made outside Nawabari do not advance this
208
+ field; consumers must use the Git-observed fields for those changes.
209
+
150
210
  `diff` requires at least one explicit concrete path and never accepts a glob or
151
211
  an empty repository-wide selection. Stats are returned by default. Patch text
152
212
  requires `--patch` and is bounded to at most 64 paths, 64 KiB, and 128 hunks;
153
213
  the caller may request smaller limits. Unrepresentable Git observations fail
154
214
  with `GIT_STATE_AMBIGUOUS`; a requested path whose stat is not exposed by Git
155
215
  remains in the result with `available: false` and makes snapshot evidence
156
- `complete: false`, so no path silently disappears.
216
+ `complete: false`, so no path silently disappears. Such paths include a
217
+ bounded `diagnostics` entry; a target directly observed as untracked reports
218
+ `reason: UNTRACKED_TARGET`, while other unavailable-stat causes remain
219
+ `STAT_UNAVAILABLE`. These diagnostics are read-only and do not stage or add
220
+ files.
157
221
 
158
222
  ## Session lifecycle
159
223
 
@@ -204,8 +268,9 @@ destructive cleanup. A clean close releases only the owned worktree and
204
268
  branch, and repeating close is idempotent. `gc` detects stale or interrupted
205
269
  sessions; `--apply` uses the same close safety checks and reports blocked
206
270
  sessions instead of guessing. `gc --dry-run` performs the same non-mutating
207
- cleanup preflight and includes stable blocker codes and `recovery_hints` for
208
- every candidate that is not safe. Cleanup revalidates the physical worktree,
271
+ cleanup preflight, reports age/physical/lifecycle suspicion separately from
272
+ destructive eligibility, and includes stable blocker codes and
273
+ `recovery_hints` for every eligible candidate that is not safe. Cleanup revalidates the physical worktree,
209
274
  branch, and `HEAD` observations immediately before each destructive Git
210
275
  operation.
211
276
 
@@ -226,12 +291,15 @@ history view; closed records remain persisted and are never silently deleted
226
291
  by listing or cleanup.
227
292
 
228
293
  `gc` stale eligibility is separate from closed-history retention. Its default
229
- threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`;
230
- records already in `stale` or `closing` state are eligible, and an otherwise
231
- live record is also eligible when Git reports its registered worktree as
232
- missing or prunable. Physical Git/worktree state is authoritative for that
233
- check. `gc --dry-run` and `gc --apply` do not treat a closed record as a stale
234
- cleanup candidate.
294
+ threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`.
295
+ Elapsed age is diagnostic suspicion only: it never authorizes destructive
296
+ cleanup for a physically healthy active session. Records already in `stale` or
297
+ `closing` state are eligible, as is an otherwise live record when Git reports
298
+ its registered worktree as safely prunable and missing. Ambiguous physical
299
+ state remains ineligible and fail-closed. `gc --dry-run` exposes suspicion and
300
+ destructive eligibility/reason separately for each candidate; `gc --apply`
301
+ uses only eligible candidates. Closed records are never stale cleanup
302
+ candidates.
235
303
 
236
304
  `doctor` includes a non-destructive `reconciliation` check. It reports
237
305
  registry/Git ownership drift, including missing or prunable worktrees and
@@ -246,16 +314,26 @@ code. Claim JSON exposes `schema_version`, `claim_id`, `session_id`, the
246
314
  repository/worktree identities, canonical `resource`, `mode`, and timestamps.
247
315
  The claim schema version is `2` and supports `read`, `write`, and
248
316
  `exclusive-write`. Schema v1 records use different overlap semantics and are
249
- not interpreted implicitly: an embedding caller must explicitly run
250
- `SessionRegistry.migrate()` before using them.
317
+ not interpreted implicitly. If an upgraded repository reports
318
+ `UNSUPPORTED_CLAIM_SCHEMA_VERSION`, run the public migration command:
319
+
320
+ ```bash
321
+ git nawabari migrate --json
322
+ ```
323
+
324
+ Migration validates the complete legacy registry under the registry lock and
325
+ rewrites it with an atomic replace. It is idempotent and retry-safe; ambiguous
326
+ or corrupt state is rejected with bounded diagnostics. Do not hand-edit or
327
+ delete the registry. Embedding callers may use the same authority through
328
+ `SessionRegistry.migrate()`.
251
329
 
252
330
  ```bash
253
331
  git nawabari session claim --session "$NAWABARI_SESSION_ID" \
254
332
  --resource 'src/**/*.ts' --mode read --json
255
333
  git nawabari session claims --session "$NAWABARI_SESSION_ID" --json
256
334
  git nawabari session update --session "$NAWABARI_SESSION_ID" \
257
- --resource 'src/**/*.ts' --mode write --json
258
- git nawabari session release --session "$NAWABARI_SESSION_ID" --json
335
+ --resource 'src/**/*.ts' --mode write --force --json
336
+ git nawabari session release --session "$NAWABARI_SESSION_ID" --all --force --json
259
337
  ```
260
338
 
261
339
  `session update` (and its `resource update` alias) atomically replaces a
@@ -280,6 +358,24 @@ complete desired set again is idempotent. A successful replacement's JSON
280
358
  exposes the resulting `claims` together with machine-readable `added` and
281
359
  `released` claims.
282
360
 
361
+ The complete public claim lifecycle is:
362
+
363
+ ```text
364
+ session claim/resource claim (additive acquire)
365
+ -> session transition/resource transition (one exact-resource mode change)
366
+ -> session mutate/resource mutate (atomic exact-resource deltas)
367
+ -> session release/resource release (--resource, --claim-id, or explicit --all)
368
+ -> session update/resource update (atomic complete-set replacement)
369
+ ```
370
+
371
+ All destructive mutations require exactly one `--if-generation` CAS or
372
+ explicit `--force`. A stale CAS returns `STALE_CLAIM_SET` without changing
373
+ claims or generation. Additive claim is not replacement; selected release
374
+ preserves unrelated claims; `--all` is the unambiguous all-claims selector.
375
+ An exact contradictory additive claim remains rejected with
376
+ `CONTRADICTORY_CLAIM` and may carry the typed `transition-exact-resource`
377
+ recovery action, whose generation is directly usable as the transition CAS.
378
+
283
379
  The modes have these normative meanings:
284
380
 
285
381
  - `read`: a non-mutating access declaration. It is not a consistency lease,
@@ -307,6 +403,14 @@ garbage-collecting a session releases its claims; no separate claim registry
307
403
  or claim lock exists. Claims describe ownership state only and do not provide
308
404
  OS-level filesystem observation or a filesystem sandbox.
309
405
 
406
+ For an existing path, canonicalization follows the physical directory entry,
407
+ so a case-insensitive filesystem cannot give `README.md` and `readme.md`
408
+ independent claim identities. This is determined from the filesystem entry
409
+ itself; paths that do not exist yet, and wildcard portions of a claim, retain
410
+ their exact lexical case because their future physical identity is unknown.
411
+ On a case-sensitive filesystem an alternate-case spelling is therefore a
412
+ distinct (possibly future) path, and no global lowercasing is applied.
413
+
310
414
  An ordinary source change uses `write` and can proceed while another session
311
415
  holds a `read` declaration:
312
416
 
@@ -359,6 +463,16 @@ identifiers, in addition to the human-readable `message`. JSON and human
359
463
  output always render the identical underlying result; only the formatting
360
464
  differs.
361
465
 
466
+ Lifecycle diagnostics additionally expose `next_action` (and the bounded
467
+ `next_actions` list) as typed, non-mutating caller actions. The action schema
468
+ is versioned independently and currently includes `retain-session`,
469
+ `supply-exact-integrated-revision`,
470
+ `retry-close-with-bounded-integration-fetch`, `discard-session`, and
471
+ `reconcile-physical-state`. A discard action always carries explicit intent;
472
+ ambiguous or terminal states never advertise destructive actions. These
473
+ fields are additive to `session-diagnostic.v1`, so existing consumers may
474
+ continue using `safe_actions`.
475
+
362
476
  - **`RESOURCE_CLAIM_CONFLICT`** (`session claim`/`session update`,
363
477
  `authorize`, `guard --operation`) reports the blocking claim
364
478
  (`ownerClaimId`, `ownerResource`, `ownerMode`) and the blocking session's
@@ -491,6 +605,16 @@ successful commit — it fails with `COMMIT_RESULT_DIVERGED`, which retains the
491
605
  resulting `commitSha` (the Git commit already happened) alongside the
492
606
  authorized, actual, and divergent path sets for recovery/reconciliation.
493
607
 
608
+ If Git reports a bounded transport failure (timeout, output limit, or spawn
609
+ failure) after the commit invocation, Nawabari re-reads the local `HEAD` and
610
+ that commit's bounded changed-path set before classifying the outcome. A
611
+ failure whose `HEAD` is unchanged is reported with `outcome: "proven-absent"`
612
+ and `retrySafe: true`; a matching new commit is returned as a successful
613
+ result with `reconciliation.outcome: "proven-committed"` and its resulting
614
+ SHA; if either observation is unavailable or does not match the authorized
615
+ paths, the failure carries `outcome: "unresolved"` and `retrySafe: false`.
616
+ Unresolved outcomes never authorize a blind retry.
617
+
494
618
  ```bash
495
619
  git nawabari commit --session "$NAWABARI_SESSION_ID" \
496
620
  --message 'record the local change' --resource src/example.ts --json
@@ -516,12 +640,19 @@ Governed push requires explicit claim-covered resources and an explicit
516
640
  `--remote`/`--branch` target. Existing upstream and local/remote relation are
517
641
  inspected before mutation. A missing upstream requires `--create-upstream`;
518
642
  behind or diverged history requires explicit `--force`. Force pushes use an
519
- exact `--force-with-lease` bound to the observed remote branch SHA; non-force
520
- pushes rely on `--no-force` without any lease option. Nawabari fetches only
521
- the explicit remote branch into a disposable ref when local ancestry is
522
- missing; it does not update tracking refs or fetch unrelated branches/tags.
643
+ exact `--force-with-lease` bound to the observed remote branch SHA. Ordinary
644
+ pushes use the same exact-generation lease, while force authorization remains
645
+ separate. A new remote branch uses an empty lease, requiring the target ref to
646
+ remain absent. Nawabari fetches only the explicit remote branch into a
647
+ disposable ref when local ancestry is missing; it does not update tracking
648
+ refs or fetch unrelated branches/tags.
523
649
  The JSON result includes the immutable `source_sha`, explicit `target_ref`,
524
- observed `observed_remote_sha`, and relation.
650
+ observed `observed_remote_sha`, and relation. If a bounded transport failure
651
+ may have happened after the remote mutation, Nawabari re-observes only that
652
+ exact remote ref. It reports `reconciliation.outcome` as `proven-pushed`,
653
+ `proven-absent`, or `unresolved`, with the exact precondition and post-failure
654
+ generation evidence. Only a proven-absent outcome is retry-safe; unresolved
655
+ outcomes never authorize a blind retry.
525
656
 
526
657
  ```bash
527
658
  git nawabari push --session "$NAWABARI_SESSION_ID" \
@@ -580,11 +711,18 @@ ID, canonical worktree and branch identities, lifecycle state, and timestamps.
580
711
 
581
712
  Ownership-changing writes use an exclusive repository-local lock and a synced
582
713
  temporary file followed by atomic replacement. Concurrent creation cannot
583
- silently duplicate an active worktree or branch. Lock recovery is conservative:
584
- the lock records a random token, PID, host, and process-start identity; an
585
- owner is reclaimed only when the same host proves that exact process identity
586
- is dead. Invalid, remote, or otherwise unverifiable lock metadata is never
587
- stolen and fails closed so an operator can inspect or remove it deliberately.
714
+ silently duplicate an active worktree or branch. Lock recovery is conservative
715
+ and platform-qualified: stale-lock reclamation is supported only on Linux,
716
+ where the lock records a random token, PID, host, and the exact process-start
717
+ token from `/proc/<pid>/stat`. An owner is reclaimed only when the same host
718
+ proves that exact process identity is dead; elapsed age and PID liveness alone
719
+ are never reclaim authority. On non-Linux platforms, Node can run ordinary
720
+ Nawabari operations but does not provide a safe process-generation identity,
721
+ so stale local locks remain `LOCK_STALE` and require deliberate operator
722
+ remediation. Invalid, remote, or otherwise unverifiable lock metadata is never
723
+ stolen and fails closed. The same limitation is machine-readable under
724
+ `capabilities --json` at the `session-lifecycle.registry_lock_recovery`
725
+ contract.
588
726
 
589
727
  ### Conformance and extraction boundary
590
728
 
@@ -609,6 +747,98 @@ The relevant Mottainai #28 execution cases are mapped as follows:
609
747
  Run `pnpm run test:package` to validate the exact packed tarball and its
610
748
  installed CLI, or `pnpm run verify` for the complete local conformance gate.
611
749
 
750
+ ## Exact packed standalone protected-execution evidence
751
+
752
+ `pnpm run test:package:protected` is the package/evidence gate for the
753
+ standalone protected product. It creates one exact `pnpm pack` archive, records its
754
+ package/version, filename, byte size, SHA-256, source revision, and host
755
+ identity in `test-artifacts/packed-standalone-protected-execution.json`,
756
+ validates the archive contents, and installs that archive into a fresh
757
+ temporary consumer with `npm install --offline`. The default evidence report
758
+ is ignored by Git; pass `--evidence-output <path>` to retain it elsewhere and
759
+ `--keep-tarball` to retain the exact archive for inspection.
760
+
761
+ The smoke test invokes only the installed `nawabari` bin. Its package allowlist
762
+ contains `dist` runtime artifacts, `README.md`, `LICENSE`, and `package.json`;
763
+ source, test, and script modules are rejected, and the installed manifest must
764
+ not declare runtime dependencies. The lifecycle proof goes through the
765
+ installed public CLI for `capabilities --json`, `doctor --json`, session
766
+ creation/resolution, `session run`, resource claims, checkpoint, commit,
767
+ local-bare-remote push, and close. The gate requires the protected doctor
768
+ report to be ready; unavailable protected execution is a failure, not a skip
769
+ or an ambient fallback. The fixture uses only a temporary local repository and
770
+ local bare remote and has no Mottainai, GitHub, `gh`, LLM, or network
771
+ dependency.
772
+
773
+ The ordinary `pnpm run test:package` command runs the package/install smoke
774
+ without requiring a real protected host, so it retains the existing
775
+ compatibility CI job; it still verifies fail-closed rejection when protection
776
+ is unavailable. The `pnpm run test:package:protected` command is the #149
777
+ evidence command and must be run on a supported Linux host. It requires a
778
+ ready protected profile and fails when that prerequisite is unavailable.
779
+
780
+ The evidence supports these boundaries only:
781
+
782
+ - Process: on Linux with the required capabilities, the canonical launcher
783
+ establishes bubblewrap user, mount, PID, IPC, and UTS namespaces, applies
784
+ the versioned seccomp profile, and drops ambient capabilities. This is not a
785
+ VM or a claim about every host process.
786
+ - Filesystem: the protected child receives the authoritative session worktree
787
+ read-write, private session HOME/cache/`/tmp`/`/proc`, repository-owned
788
+ shared HOME state, and the fixed read-only runtime/tool inputs selected by
789
+ the profile. Sibling worktrees and unselected host HOME paths are not part
790
+ of the selected topology.
791
+ - HOME/cache: the child sees `/home/nawabari`; private state is per session,
792
+ while only the repository's selected shared-home subtree is shared. Selected
793
+ host tool directories are read-only inputs, not the host HOME.
794
+ - Network: `network_mode` is `inherited`; this evidence does not prove egress
795
+ isolation.
796
+ - Linux prerequisites: use `nawabari doctor --json` on a supported Linux host.
797
+ The package's supported Node.js engine, Git, and required bubblewrap,
798
+ namespace, seccomp, and capability support must be available and ready;
799
+ cgroups v2 and Landlock remain profile-reported optional defenses.
800
+ - Failure behavior: protected resolution uses `enforce: true`; missing
801
+ required capability, unsupported topology, or launch failure returns a
802
+ bounded failure and never retries through ambient execution.
803
+
804
+ ## Packed Mottainai preselected-UID handoff evidence
805
+
806
+ `pnpm run test:package:mottainai` is the required-CI external-handoff gate for
807
+ Issue #150. `scripts/run-mottainai-uid-handoff-gate.mjs` runs the #149
808
+ protected package gate exactly once, retains that exact tarball and its
809
+ `nawabari.packed-standalone-protected-execution.v1` evidence, and passes only
810
+ those exact paths into `scripts/run-mottainai-uid-handoff.mjs`, which installs
811
+ that same tarball into a disposable consumer. `run-mottainai-uid-handoff.mjs`
812
+ itself has no self-pack path: it always requires an already-produced
813
+ `--tarball`/`--artifact-evidence` pair, so #150 can never hide a stale or
814
+ mismatched artifact behind a second pack. The fixture invokes the installed
815
+ `session run` contract under two distinct preselected unprivileged UIDs
816
+ (23001 and 23002 by default) and records bounded
817
+ artifact/fixture/UID/session/worktree/resource/Git evidence in
818
+ `test-artifacts/mottainai-packed-uid-handoff.json`. This gate runs in required
819
+ CI on a Linux host with bubblewrap (`mottainai-uid-handoff` job).
820
+
821
+ The checked-in `scripts/test-fixtures/mottainai-preselected-uid-runner.sh` is a
822
+ UID-only execution adapter. A real Mottainai Runtime may supply another
823
+ executable with the same `--uid`, `--root`, `--cwd`, `--` argv contract:
824
+
825
+ ```bash
826
+ node scripts/run-mottainai-uid-handoff.mjs \
827
+ --tarball nawabari-0.7.1.tgz \
828
+ --artifact-evidence test-artifacts/packed-standalone-protected-execution.json \
829
+ --fixture-runner /path/to/mottainai-uid-runner \
830
+ --uids 23001,23002 \
831
+ --output test-artifacts/mottainai-packed-uid-handoff.json
832
+ ```
833
+
834
+ The runner supplies only the caller's OS UID/GID view. Nawabari remains the
835
+ sole authority for session, worktree, resource claims, protected execution,
836
+ checkpoint, commit, push, local Git, and close; the fixture creates no
837
+ repository-principal registry and passes no Mottainai task, policy, or
838
+ credential semantics. Identical display names, branch names, and resource
839
+ paths are used under different canonical repository paths, and the protected
840
+ child's session/cwd marker is checked to prevent principal confusion.
841
+
612
842
  ## Development
613
843
 
614
844
  ```bash
@@ -0,0 +1,44 @@
1
+ export type CliHelpOptionSpec = {
2
+ readonly name: string;
3
+ readonly aliases?: readonly string[];
4
+ readonly alias_of?: string;
5
+ readonly value?: string;
6
+ readonly required?: boolean;
7
+ readonly default?: string;
8
+ readonly minimum?: number;
9
+ readonly maximum?: number;
10
+ readonly repeatable?: boolean;
11
+ readonly description: string;
12
+ };
13
+ export type CliCommandDefinition = {
14
+ readonly name: string;
15
+ readonly aliases?: readonly string[];
16
+ readonly summary: string;
17
+ readonly usage: string;
18
+ readonly options: readonly CliHelpOptionSpec[];
19
+ readonly notes?: readonly string[];
20
+ };
21
+ export declare const GLOBAL_HELP_OPTIONS: readonly CliHelpOptionSpec[];
22
+ /**
23
+ * Canonical public command/discovery registry.
24
+ *
25
+ * Dispatcher implementation deliberately remains below in this module. This
26
+ * registry only describes the public discovery surface; aliases point at the
27
+ * canonical command so option metadata cannot drift between projections.
28
+ */
29
+ export declare const CLI_COMMAND_REGISTRY: readonly CliCommandDefinition[];
30
+ export declare const ROOT_HELP_SPEC: CliCommandDefinition;
31
+ /** Resolve a public name through the one canonical registry. */
32
+ export declare function canonicalCommandForName(name: string): CliCommandDefinition | undefined;
33
+ /**
34
+ * Materialize public names for projections without copying option metadata.
35
+ * Alias entries are generated from their canonical definition, including the
36
+ * usage, options, and parser notes.
37
+ */
38
+ export declare function publicCliCommandDefinitions(): readonly CliCommandDefinition[];
39
+ /** Stable alias for consumers such as the future dispatcher parity layer. */
40
+ export declare const COMMAND_REGISTRY: readonly CliCommandDefinition[];
41
+ /** Resolve either a canonical command or one of its public aliases. */
42
+ export declare function resolveCliCommandDefinition(name: string): CliCommandDefinition | undefined;
43
+ /** Return the complete public discovery name list in registry order. */
44
+ export declare function publicCliCommandNames(): readonly string[];