@quantiya/codevibe-core 2.0.92 → 2.0.93

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 (79) hide show
  1. package/dist/adapter/registry.d.ts +2 -2
  2. package/dist/appsync/sync-deferred-inputs.d.ts +2 -2
  3. package/dist/appsync/sync-desktop-registration.d.ts +2 -2
  4. package/dist/appsync/sync-mobile-echo.d.ts +16 -16
  5. package/dist/appsync/sync-prompt-publication.d.ts +8 -8
  6. package/dist/appsync/sync-shell-source.d.ts +18 -18
  7. package/dist/appsync/sync-source-cursor.d.ts +30 -30
  8. package/dist/commerce/upgrade-handoff.d.ts +11 -0
  9. package/dist/companion-mode/codex-native-state.d.ts +110 -110
  10. package/dist/index.js +2605 -2306
  11. package/dist/local-executor/anchored-cas.d.ts +78 -6
  12. package/dist/local-executor/anchored-fs.d.ts +32 -0
  13. package/dist/local-executor/blob-store.d.ts +95 -0
  14. package/dist/local-executor/change-manifest-store.d.ts +32 -0
  15. package/dist/local-executor/change-manifest.d.ts +50 -0
  16. package/dist/local-executor/durable-store.d.ts +72 -15
  17. package/dist/local-executor/implementor-argv.d.ts +43 -7
  18. package/dist/local-executor/local-executor-impl.d.ts +57 -1
  19. package/dist/local-executor/nolink-apply.d.ts +22 -2
  20. package/dist/local-executor/revert-runner.d.ts +3 -0
  21. package/dist/local-executor/shadow-apply-capabilities.d.ts +31 -2
  22. package/dist/local-executor/shadow-free-space.d.ts +25 -0
  23. package/dist/local-executor/shadow-protocol.d.ts +117 -18
  24. package/dist/local-executor/shadow-recovery.d.ts +28 -3
  25. package/dist/local-executor/shadow-task-intent.d.ts +29 -0
  26. package/dist/local-executor/shadow-volume-probe.d.ts +0 -14
  27. package/dist/local-executor/sharded-list.d.ts +32 -0
  28. package/dist/local-executor/snapshot-merge.d.ts +18 -0
  29. package/dist/local-executor/spawn-sweep-binary.d.ts +33 -0
  30. package/dist/local-executor/spawn-sweep.d.ts +3 -0
  31. package/dist/local-executor/store-dir-anchor.d.ts +48 -0
  32. package/dist/local-executor/tier-self-test.d.ts +57 -0
  33. package/dist/local-executor/types.d.ts +26 -0
  34. package/dist/local-executor/verification-gates/diff-sanity.d.ts +6 -0
  35. package/dist/local-executor/workspace-materializer.d.ts +17 -1
  36. package/dist/local-executor/workspace-shadow.d.ts +209 -7
  37. package/dist/orchestration-shell/audit-summary.d.ts +22 -0
  38. package/dist/orchestration-shell/bounded-shutdown.d.ts +2 -0
  39. package/dist/orchestration-shell/cli.js +64053 -57609
  40. package/dist/orchestration-shell/components/OrchestrationApp.d.ts +6 -1
  41. package/dist/orchestration-shell/decision-attempt-recovery.d.ts +14 -0
  42. package/dist/orchestration-shell/engine-projection.d.ts +98 -0
  43. package/dist/orchestration-shell/exit-guard.d.ts +18 -0
  44. package/dist/orchestration-shell/gate-decision-submit.d.ts +2 -0
  45. package/dist/orchestration-shell/index.d.ts +18 -0
  46. package/dist/orchestration-shell/quorum-loop.d.ts +265 -21
  47. package/dist/orchestration-shell/review-bundle.d.ts +77 -0
  48. package/dist/orchestration-shell/review-receipts.d.ts +100 -0
  49. package/dist/orchestration-shell/reviewer-seat-self-test.d.ts +43 -0
  50. package/dist/orchestration-shell/team-decompose.d.ts +1 -0
  51. package/dist/orchestration-shell/types.d.ts +24 -0
  52. package/dist/reviewer/provider.d.ts +28 -0
  53. package/dist/reviewer/providers/antigravity.d.ts +4 -2
  54. package/dist/reviewer/providers/claude.d.ts +7 -2
  55. package/dist/reviewer/providers/codex.d.ts +1 -1
  56. package/dist/session-resume/cli-resume.d.ts +1 -0
  57. package/dist/substrate/bwrap.d.ts +85 -0
  58. package/dist/substrate/command-runner.d.ts +1 -0
  59. package/dist/substrate/docker.d.ts +21 -0
  60. package/dist/substrate/grant-rule.d.ts +71 -0
  61. package/dist/substrate/pasta-isolation-check.d.ts +60 -0
  62. package/dist/substrate/pasta.d.ts +8 -0
  63. package/dist/substrate/relay-channel.d.ts +80 -0
  64. package/dist/substrate/review-mask.d.ts +20 -0
  65. package/dist/substrate/sandbox-exec.d.ts +48 -1
  66. package/dist/substrate/seatbelt-grants.d.ts +46 -0
  67. package/dist/substrate/spawn-records.d.ts +95 -0
  68. package/dist/substrate/tier-relay-main.d.ts +1 -0
  69. package/dist/substrate/tier-relay.d.ts +7 -0
  70. package/dist/substrate/tier-relay.js +3 -0
  71. package/dist/substrate/types.d.ts +48 -1
  72. package/dist/substrate-launch/engage-substrate.d.ts +16 -2
  73. package/dist/substrate-launch/fallback-ladder.d.ts +109 -12
  74. package/dist/substrate-launch/index.d.ts +1 -1
  75. package/dist/substrate-launch/task-folder.d.ts +26 -0
  76. package/native/linux-arm64/codevibe-spawn-sweep +0 -0
  77. package/native/linux-x64/codevibe-spawn-sweep +0 -0
  78. package/native/spawn-sweep.json +8 -0
  79. package/package.json +10 -5
@@ -0,0 +1,60 @@
1
+ export interface ProbeTarget {
2
+ host: string;
3
+ port: number;
4
+ /** What the target is: a loopback address or a gateway, at a host listener's port. */
5
+ kind: 'loopback' | 'gateway';
6
+ /** The host listener the port belongs to. */
7
+ listener: string;
8
+ }
9
+ export interface ProbeOutcome extends ProbeTarget {
10
+ connected: boolean;
11
+ }
12
+ export interface IsolationEvidence {
13
+ probes: ProbeOutcome[];
14
+ /** Connections each host listener recorded, by listener name. */
15
+ listenerConnections: Record<string, number>;
16
+ }
17
+ export interface IsolationVerdict {
18
+ pass: boolean;
19
+ reasons: string[];
20
+ }
21
+ /** The exact §6.2 `-T none` rule, as a pure function of what the probe and the listeners observed. */
22
+ export declare function evaluateIsolation(e: IsolationEvidence): IsolationVerdict;
23
+ /**
24
+ * The host's IPv6 state, stable fields only (S1R15-N2): each `ip -6 route` entry's destination, gateway and device,
25
+ * each `inet6` address with its prefix length and scope — not lifetimes / expiry counters, not temporary addresses.
26
+ */
27
+ export declare function ipv6StateDigest(routeText: string, addrText: string): string;
28
+ export declare function isolationCacheKey(pastaSha256: string, ipv6Digest: string): string;
29
+ /** The probe, run with Node inside the pasta namespace. argv[1] = JSON of the host listeners' ports to try. */
30
+ export declare const ISOLATION_PROBE_SOURCE: string;
31
+ export interface RunIsolationCheckOptions {
32
+ pastaPath?: string;
33
+ nodePath?: string;
34
+ desktopPid?: number;
35
+ timeoutMs?: number;
36
+ /** Evidence controls only (e.g. the design's run without `--no-map-gw`, which must fail): rewrites the pasta argv. */
37
+ mutatePastaArgv?: (argv: string[]) => string[];
38
+ }
39
+ /** Runs the check on this host (Linux, pasta installed) and returns the evidence and verdict. */
40
+ /**
41
+ * F-PASTA-SELINUX-STDIO: a probe that exits 0 having printed nothing did not reach its verdict. On SELinux enforcing the
42
+ * passt policy closes a socket inherited across pasta's domain transition, and Node's child stdio is a socket, so the child
43
+ * runs with no stdin, stdout or stderr. The installer loads the one rule that allows it.
44
+ */
45
+ export declare function parseIsolationProbeOutput(stdout: string): ProbeOutcome[];
46
+ export declare function runIsolationCheck(opts?: RunIsolationCheckOptions): Promise<{
47
+ evidence: IsolationEvidence;
48
+ verdict: IsolationVerdict;
49
+ }>;
50
+ export interface IsolationCacheEntry {
51
+ key: string;
52
+ pastaSha256: string;
53
+ ipv6Digest: string;
54
+ pass: boolean;
55
+ reasons: string[];
56
+ checkedAt: string;
57
+ }
58
+ /** Cached results beside the §6.4 tier self-test results: `<dir>/pasta-isolation-<key>.json`. */
59
+ export declare function readIsolationCache(dir: string, key: string): IsolationCacheEntry | null;
60
+ export declare function writeIsolationCache(dir: string, entry: IsolationCacheEntry): void;
@@ -28,6 +28,14 @@ export declare function buildSetprivPrefix(desktopPid?: number): string[];
28
28
  * Closes all fds above 2, opens filter as fd 9, and execs bwrap with --seccomp 9.
29
29
  */
30
30
  export declare const IN_TIER_CLEANUP_PASTA = "for f in /proc/self/fd/*; do n=${f##*/}; case $n in 0|1|2) ;; *) eval \"exec $n>&-\" 2>/dev/null;; esac; done; exec 9<\"$0\" && exec bwrap --seccomp 9 \"$@\"";
31
+ /**
32
+ * §6.2 broker tier — the in-tier command run by bubblewrap: start the relay on the inherited fd 3, wait (bounded) for
33
+ * its ready file, close fd 3, then exec the agent, so the agent holds exactly fds 0–2 whatever was inherited.
34
+ * argv after the script: <node> <relay script> <port> <ready file> <agent argv…>. Exit 98: the relay never got ready.
35
+ */
36
+ export declare const IN_TIER_RELAY_STEP: string;
37
+ /** Where the relay's ready file lives inside the tier (the spawn's private `/tmp`). */
38
+ export declare const IN_TIER_RELAY_READY_FILE = "/tmp/.codevibe-relay-ready";
31
39
  /**
32
40
  * In-tier bash step for the isolated broker tier (no pasta):
33
41
  * Keeps fds 0-3 (fd 3 is the broker relay socketpair), closes fds above 3,
@@ -0,0 +1,80 @@
1
+ import net from 'node:net';
2
+ import type { Duplex } from 'node:stream';
3
+ export declare const RELAY_MAX_FRAME_BYTES = 65536;
4
+ export declare const RELAY_MAX_STREAM_ID: number;
5
+ export declare const RELAY_MAX_OWNED_SOCKETS = 64;
6
+ export declare const RELAY_MAX_OUTBOUND_BYTES: number;
7
+ export declare const RELAY_MAX_STREAM_BUFFER_BYTES: number;
8
+ export declare const RELAY_HALF_CLOSED_CAP = 256;
9
+ export declare const RELAY_PARTIAL_FRAME_DEADLINE_MS = 5000;
10
+ export declare const RELAY_LINGER_MS = 5000;
11
+ export interface RelayChannelOptions {
12
+ /** Opens the desktop's OWN connection to its OWN broker — the only address this channel ever dials. */
13
+ connectBroker: () => net.Socket;
14
+ maxOutboundBytes?: number;
15
+ partialFrameDeadlineMs?: number;
16
+ lingerMs?: number;
17
+ onClose?: (reason: string | null) => void;
18
+ }
19
+ export interface RelayChannelStats {
20
+ streams: number;
21
+ ownedHighWater: number;
22
+ owned: number;
23
+ halfClosedFrames: number;
24
+ halfClosedSetSize: number;
25
+ relayClosesReceived: number;
26
+ droppedForRelayClosed: number;
27
+ pausedToBroker: number;
28
+ resumedOnClose: number;
29
+ protocolErrors: number;
30
+ outboundHighWater: number;
31
+ channelDrainListeners: number;
32
+ closeReason: string | null;
33
+ }
34
+ export declare class RelayChannel {
35
+ private readonly channel;
36
+ private readonly connectBroker;
37
+ private readonly maxOutbound;
38
+ private readonly partialDeadlineMs;
39
+ private readonly lingerMs;
40
+ private readonly onClose?;
41
+ private buffer;
42
+ private highestId;
43
+ private readonly streams;
44
+ /** Ids the desktop closed: late frames discarded; insertion-ordered for oldest-first eviction. */
45
+ private readonly halfClosed;
46
+ /**
47
+ * Recently relay-closed ids, for the refusal's reason only: every id at or below the highest that is neither live nor
48
+ * half-closed is refused anyway (ids strictly increase), so this stays bounded (oldest-first, 256).
49
+ */
50
+ private readonly relayClosedIds;
51
+ /** Every broker socket the desktop owns, from connect() until it actually closes. */
52
+ private readonly owned;
53
+ /** Broker sockets paused because the channel's outbound side is full; resumed by the one channel drain listener. */
54
+ private readonly pausedForChannel;
55
+ /** The stream whose broker write filled up: the channel is paused until it drains or closes. */
56
+ private pauseOwner;
57
+ private partialTimer;
58
+ private closed;
59
+ private readonly stats;
60
+ constructor(channel: Duplex, opts: RelayChannelOptions);
61
+ snapshot(): RelayChannelStats;
62
+ /** Close the channel and every broker socket it owns (teardown, or a protocol error with its reason). */
63
+ close(reason: string | null): void;
64
+ private onChannelData;
65
+ private rememberRelayClosed;
66
+ private parse;
67
+ private onFrame;
68
+ private openStream;
69
+ private toBroker;
70
+ private onBrokerDrain;
71
+ private releasePause;
72
+ private relayClose;
73
+ private fromBroker;
74
+ private sendFrame;
75
+ private onChannelDrain;
76
+ private brokerEnded;
77
+ private brokerClosed;
78
+ private armPartialDeadline;
79
+ private clearPartialDeadline;
80
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * P63 Slice 2 — §8.2 the macOS masked original (W5).
3
+ *
4
+ * A macOS reviewer seat may read the user's folder for context, read-only, with these kinds made unreadable by
5
+ * trailing Seatbelt denies (last match wins, as for `<workdir>/.git` and `~/.codevibe` in `sandbox-exec.ts`):
6
+ * secrets (`.env`, `.env.*`, `id_*`, `.aws/credentials`, `*.pem`, `*.key`, `*.crt`, `*.pfx`, `*.p12`, `*.keystore`),
7
+ * credential configs (`.npmrc`, `.pypirc`, `.netrc`, `.yarnrc.yml`), CodeVibe-internal names (`.codevibe*`,
8
+ * `.agent-worktrees`, `.review-worktrees`, `.hard-gate`, `*-home`), audio and video by extension, and `<original>/.git`.
9
+ * They are PATTERNS anchored at the regex-escaped original — so they match at any depth and also cover files created
10
+ * after the profile was written — with every letter written per character (`[mM][pP]4`) for case-insensitive volumes.
11
+ * Every changed file whose bytes the bundle withholds gets a per-file `literal` deny; more than
12
+ * {@link MAX_WITHHELD_LITERALS} of them means no masked original at all — the seat gets the bundle only, as on Linux.
13
+ */
14
+ /** §8.2 / SP-A4j: 1,000 literals added ~0.4 s to profile start; 5,000 ~8.8 s; 20,000 never started. */
15
+ export declare const MAX_WITHHELD_LITERALS = 1000;
16
+ /**
17
+ * The trailing deny lines masking `originalDir` for a reviewer, or null when `changedWithheld` (paths relative to the
18
+ * original) exceeds {@link MAX_WITHHELD_LITERALS} — the caller then grants the bundle only.
19
+ */
20
+ export declare function reviewMaskDenyLines(originalDir: string, changedWithheld: readonly string[]): string[] | null;
@@ -1,4 +1,5 @@
1
1
  import type { CommandRunner, Substrate, SubstrateHandle, SubstrateSpec } from './types';
2
+ import { type SeatbeltOwnFolder } from './seatbelt-grants';
2
3
  /** Injectable deps — default-wired to the real runtime; stubbed in unit tests. */
3
4
  export interface SandboxExecDeps {
4
5
  /** Process layer (default: `node:child_process`). */
@@ -66,6 +67,33 @@ export interface SandboxExecDeps {
66
67
  * @param execPath the Node interpreter to grant read on (default
67
68
  * `process.execPath`); injectable for deterministic tests.
68
69
  */
70
+ /** P63 §6.1 — what a Seatbelt profile needs for the grant rule, the credential block and the hide block. */
71
+ export interface SeatbeltProfileWorkspace {
72
+ project: string;
73
+ copyRoots: string[];
74
+ ownFolders: SeatbeltOwnFolder[];
75
+ toolchainGrants: string[];
76
+ nodeGrants: string[];
77
+ }
78
+ /**
79
+ * The shipped trailing denies, re-emitted after every hide block (in the profile and in the exec-time append): the
80
+ * workdir's `.git`, `~/.codevibe`, the reviewer's bundle/original write denies and masks, the plan-mode write deny and
81
+ * the audit dir.
82
+ */
83
+ export declare function seatbeltTrailingDenyLines(opts: {
84
+ workdir: string;
85
+ homedir: string;
86
+ reviewGrants: {
87
+ bundleDir: string;
88
+ maskedOriginal?: {
89
+ dir: string;
90
+ changedWithheld: readonly string[];
91
+ };
92
+ } | null;
93
+ maskLines: string[] | null;
94
+ writableWorkdir: boolean;
95
+ auditDir: string | null;
96
+ }): string[];
69
97
  export declare function buildSeatbeltProfile(workdir: string, execPath?: string,
70
98
  /**
71
99
  * CP-7 PART D — extra READ-ONLY dirs to grant (the apiKeyHelper bootstrap dir
@@ -116,7 +144,24 @@ networkPosture?: 'loopback' | 'open',
116
144
  * When false, denies writes to the worktree, establishing a hard read-only boundary (R5-F1).
117
145
  * Default true (writes permitted to workdir).
118
146
  */
119
- writableWorkdir?: boolean): string;
147
+ writableWorkdir?: boolean,
148
+ /**
149
+ * P63 Slice 2 (§8.2) — a reviewer seat's read-only grants: the review bundle, and (macOS) the user's original folder
150
+ * masked by {@link reviewMaskDenyLines}. The seat's `workdir` is its private scratch. When the mask cannot be built
151
+ * (more than 1,000 changed withheld files) the original is not granted at all — bundle only, as on Linux.
152
+ */
153
+ reviewGrants?: {
154
+ bundleDir: string;
155
+ maskedOriginal?: {
156
+ dir: string;
157
+ changedWithheld: readonly string[];
158
+ };
159
+ } | null,
160
+ /**
161
+ * P63 §6.1 — the read-grant rule, credential block and hide block. Absent → the pre-Slice-2 profile (unit stubs).
162
+ * `toolchainGrants` are already through the grant rule (`seatbeltToolchainGrants`).
163
+ */
164
+ workspace?: SeatbeltProfileWorkspace | null): string;
120
165
  /**
121
166
  * A5 macOS `sandbox-exec` substrate (`egressFidelity: 'coarse'`). See file
122
167
  * header. NOT the default — the LE selects it as the macOS Docker-fallback.
@@ -127,4 +172,6 @@ export declare class SandboxExecSubstrate implements Substrate {
127
172
  private readonly platform;
128
173
  constructor(deps?: SandboxExecDeps);
129
174
  launch(spec: SubstrateSpec): Promise<SubstrateHandle>;
175
+ /** P63 §6.1 — the seat's own folders, the toolchain and interpreter grants (through the rule), and the PATH. */
176
+ private seatbeltWorkspace;
130
177
  }
@@ -0,0 +1,46 @@
1
+ import { type GrantRuleInput } from './grant-rule';
2
+ /** Regex-escapes a literal path for a Seatbelt `(regex #"…")` (POSIX ERE metacharacters). */
3
+ export declare function seatbeltRegexEscape(literal: string): string;
4
+ /** A literal name as a case-independent pattern: letters become `[xX]`, regex metacharacters are escaped. */
5
+ export declare function caseFree(literal: string): string;
6
+ /** §6.1 — the credential names hidden inside granted roots, as case-free pattern fragments for one path component. */
7
+ export declare function credentialNamePatterns(): {
8
+ anyDepth: string[];
9
+ rootOnly: string[];
10
+ pemAllow: string[];
11
+ };
12
+ /**
13
+ * The credential block for these roots: denies of every credential name at any depth (and `.docker/config.json`
14
+ * anywhere, `config.json` at the root), then the CA-bundle re-allows (read only).
15
+ */
16
+ export declare function credentialBlockLines(roots: readonly string[]): string[];
17
+ export interface SeatbeltOwnFolder {
18
+ dir: string;
19
+ mode: 'read' | 'read-write';
20
+ }
21
+ /** The hide block: deny the project and every copy root, re-allow this seat's own folders, metadata re-allows. */
22
+ export declare function hideBlockLines(opts: {
23
+ project: string;
24
+ copyRoots: readonly string[];
25
+ ownFolders: readonly SeatbeltOwnFolder[];
26
+ }): string[];
27
+ /**
28
+ * §6.1 (S1R8-N3) — true when some component of `p` has a sibling whose name differs only in letter case (possible only
29
+ * on a case-sensitive volume, where a Seatbelt grant of one would also cover the other).
30
+ */
31
+ export declare function hasCaseVariantSibling(p: string, readdir?: (dir: string) => string[]): boolean;
32
+ /**
33
+ * §6.1 "Letter case" for CodeVibe's own folders and a reviewer's original (S2-C2, Stage 2 R1): the reason to refuse a
34
+ * grant, or null. Unlike a toolchain entry, which can simply be left out, these folders cannot be dropped, so a path
35
+ * whose case cannot be checked (a parent that cannot be listed) is refused as well.
36
+ */
37
+ export declare function ownFolderCaseRefusal(p: string, readdir?: (dir: string) => string[]): string | null;
38
+ /** The macOS toolchain grants: the shared rule, then the case-variant refusal. */
39
+ export declare function seatbeltToolchainGrants(input: Omit<GrantRuleInput, 'platform'>, caseCheck?: (p: string) => boolean): {
40
+ grants: string[];
41
+ refused: Array<{
42
+ path: string;
43
+ reason: string;
44
+ }>;
45
+ sandboxPath: string[];
46
+ };
@@ -0,0 +1,95 @@
1
+ import { type ProcessIdentity } from '../local-executor/process-identity';
2
+ import { type SpawnSweepRecord, type SpawnSweepResult } from '../local-executor/spawn-sweep';
3
+ export declare const SPAWN_RECORD_FILE_RE: RegExp;
4
+ export type SpawnFolderKind = 'spawn' | 'agy-home' | 'advisory';
5
+ export interface SpawnStartRecord {
6
+ version: 1;
7
+ spawnId: string;
8
+ /** null for an advisory turn. */
9
+ taskId: string | null;
10
+ kind: SpawnFolderKind;
11
+ /** The spawn's folder, by exact path. */
12
+ folder: string;
13
+ /** The copy location the folder lies under (one of the §5.1 copy roots). */
14
+ copyRoot: string;
15
+ platform: NodeJS.Platform;
16
+ /** Linux only — `/proc/sys/kernel/random/boot_id`. */
17
+ bootId?: string;
18
+ /** Linux only — the exact `bwrap …` vector the chain execs. */
19
+ sandboxArgv?: string[];
20
+ owner: ProcessIdentity;
21
+ /** Added once `spawn()` returned (the chain is detached, so pid = pgid = sid). */
22
+ pgid?: number;
23
+ pid?: number;
24
+ sid?: number;
25
+ leaderStarttime?: string;
26
+ }
27
+ /** The record's own path: the task's state directory, or `advisory/` for an advisory turn. */
28
+ export declare function spawnRecordPath(stateRoot: string, taskId: string | null, spawnId: string): string;
29
+ /** The §5.1 folder for a spawn — the only two forms (three with macOS agy-home) the sweep accepts (S1R13-L1). */
30
+ export declare function spawnFolderPath(copyRoot: string, taskId: string | null, spawnId: string, kind: SpawnFolderKind): string;
31
+ /** The filter beside the record (Linux): `filter-<spawn>.bpf` in the same state directory. */
32
+ export declare function spawnFilterPath(stateRoot: string, taskId: string | null, spawnId: string): string;
33
+ /**
34
+ * The spawn's durable start record, before the spawn: exclusive, mode 0600, synced with its directory. The folder
35
+ * must be one of the exact §5.1 forms for this record's task and spawn.
36
+ */
37
+ export declare function writeSpawnStartRecord(stateRoot: string, input: Omit<SpawnStartRecord, 'version' | 'owner' | 'platform'> & {
38
+ owner?: ProcessIdentity;
39
+ platform?: NodeJS.Platform;
40
+ }): Promise<string>;
41
+ /**
42
+ * A tier that adopts a caller-seeded folder (`SubstrateSpec.spawnFolder`) completes the caller's start record with the
43
+ * exact sandbox argv and boot id, before its spawn, instead of writing a second record for the same spawn. The record
44
+ * must already name this spawn, task, kind and folder.
45
+ */
46
+ export declare function completeAdoptedSpawnRecord(stateRoot: string, base: Pick<SpawnStartRecord, 'spawnId' | 'taskId' | 'kind' | 'folder' | 'copyRoot'>, fields: {
47
+ sandboxArgv: string[];
48
+ bootId: string;
49
+ platform: NodeJS.Platform;
50
+ }): Promise<string>;
51
+ export declare function readSpawnRecord(file: string): Promise<SpawnStartRecord | null>;
52
+ export interface SpawnProcStat {
53
+ pgrp: number;
54
+ session: number;
55
+ starttime: string;
56
+ }
57
+ /** Linux `/proc/<pid>/stat`: fields 5 (pgrp), 6 (session) and 22 (starttime), or null. */
58
+ export declare function readSpawnProcStat(pid: number): SpawnProcStat | null;
59
+ /**
60
+ * As soon as `spawn()` returned (§6.2, S1R20-F3): the chain is started detached, so it should be its own session and
61
+ * group. The record-time invariant (S1R39-F8): only when the spawn's pgid AND sid both equal its own pid is the group
62
+ * recorded (with the leader's starttime); otherwise the record carries NO group and the sweep resolves the spawn by
63
+ * its sandbox argv alone. A mis-read is fail-safe — a group holding no argv witness never opens.
64
+ */
65
+ export declare function recordSpawnGroup(file: string, pid: number, stat: SpawnProcStat | null): Promise<boolean>;
66
+ /**
67
+ * The helper's stdin record (§6.2). The folder needle is the exact §5.1 form WITH its trailing separator
68
+ * (`…/<task>/spawn/<spawn>/`, `…/advisory-<spawn>/`): the helper refuses any other form, and the separator is what keeps
69
+ * spawn `abc`'s needle from matching a command line that names spawn `abcd`.
70
+ */
71
+ export declare function sweepRecordOf(record: SpawnStartRecord): SpawnSweepRecord;
72
+ export interface SpawnRemovalDeps {
73
+ runSweep?: (record: SpawnSweepRecord, platform: NodeJS.Platform) => Promise<SpawnSweepResult>;
74
+ }
75
+ /**
76
+ * Ends one recorded spawn's folders: on Linux the spawn sweep first (when `sweep` is set — the spawn did not end by
77
+ * the agent's own exit, or the record is an orphan), then the folder, the filter and the record, by exact path.
78
+ * Returns false (and removes nothing) when the sweep reports anything still owned or a blocker.
79
+ */
80
+ export declare function removeRecordedSpawn(stateRoot: string, record: SpawnStartRecord, opts: {
81
+ sweep: boolean;
82
+ } & SpawnRemovalDeps): Promise<boolean>;
83
+ export interface OrphanSpawnSweepResult {
84
+ removed: string[];
85
+ /** Tasks whose spawn could not be cleared — the caller keeps their state (the next start retries). */
86
+ blockedTaskIds: Set<string>;
87
+ retained: string[];
88
+ }
89
+ /**
90
+ * Desktop start: every record whose owning desktop no longer runs is an orphan — swept (Linux) and removed by exact
91
+ * path. A record of a live desktop (another CodeVibe session) is left alone, and so is its task.
92
+ */
93
+ export declare function sweepOrphanSpawnRecords(stateRoot: string, deps?: SpawnRemovalDeps & {
94
+ isOwnerAlive?: (owner: ProcessIdentity) => Promise<boolean>;
95
+ }): Promise<OrphanSpawnSweepResult>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import net from 'node:net';
2
+ export declare function runTierRelay(opts: {
3
+ channelFd: number;
4
+ port: number;
5
+ readyFile: string;
6
+ host?: string;
7
+ }): net.Server;
@@ -0,0 +1,3 @@
1
+ "use strict";var R=Object.create;var E=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var M=Object.getPrototypeOf,_=Object.prototype.hasOwnProperty;var N=(n,r,s,c)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of A(r))!_.call(n,t)&&t!==s&&E(n,t,{get:()=>r[t],enumerable:!(c=U(r,t))||c.enumerable});return n};var I=(n,r,s)=>(s=n!=null?R(M(n)):{},N(r||!n||!n.__esModule?E(s,"default",{value:n,enumerable:!0}):s,n));var k=I(require("node:fs")),b=I(require("node:net")),B=65536,f=8;function x(n){let r=new b.default.Socket({fd:n.channelFd,readable:!0,writable:!0}),s=new Map,c=1,t=Buffer.alloc(0),a=null,y=new Set,v=(e,o,i)=>{let l=Buffer.alloc(f);l.writeUInt32BE(o.length,0),l.writeUInt32BE(e,4),!r.write(o.length>0?Buffer.concat([l,o]):l)&&i&&!i.destroyed&&(i.pause(),y.add(i))};r.on("drain",()=>{for(let e of y)e.destroyed||e.resume();y.clear()});let F=()=>{for(;t.length>=f;){let e=t.readUInt32BE(0),o=t.readUInt32BE(4);if(e>B)return d();if(t.length<f+e)return;let i=t.subarray(f,f+e);t=t.subarray(f+e);let l=s.get(o);if(l){if(e===0){s.delete(o),l.end(),a===l&&h();continue}!l.write(i)&&a===null&&(a=l,r.pause())}}},h=()=>{a=null,r.resume(),F()};r.on("data",e=>{t=t.length===0?e:Buffer.concat([t,e]),F()});let p=b.default.createServer(e=>{let o=0,i=()=>(o===0&&(o=c++,s.set(o,e)),o);e.on("data",m=>{let S=i();if(s.has(S))for(let u=0;u<m.length;u+=B)v(S,m.subarray(u,u+B),e)}),e.on("drain",()=>{a===e&&h()});let l=()=>{o!==0&&(s.get(o)===e&&(s.delete(o),v(o,Buffer.alloc(0),null)),a===e&&h())};e.on("end",l),e.on("close",l),e.on("error",()=>e.destroy())}),d=()=>{for(let e of s.values())e.destroy();s.clear(),p.close(),r.destroy()};return r.on("end",d),r.on("close",d),r.on("error",d),p.listen(n.port,n.host??"127.0.0.1",()=>{k.writeFileSync(n.readyFile,`${process.pid}
2
+ `,{mode:384,flag:"wx"})}),p}var g=Number(process.argv[2]),w=process.argv[3];(!Number.isInteger(g)||g<=0||g>65535||typeof w!="string"||w.length===0)&&(process.stderr.write(`tier-relay: usage: tier-relay <port> <ready file>
3
+ `),process.exit(2));x({channelFd:3,port:g,readyFile:w});
@@ -32,6 +32,19 @@ export interface SubstrateSpec {
32
32
  workdir: string;
33
33
  /** Exact host workdir incarnation for task-scoped mutable launches. */
34
34
  workdirAuthority?: WorkspaceRootAuthority;
35
+ /**
36
+ * P63 Slice 2 (§8.2) — a reviewer seat's read-only grants; `workdir` is then the seat's PRIVATE scratch. The review
37
+ * bundle is granted read-only on every backend; `maskedOriginal` (the user's folder for context, with the §8.2
38
+ * secret / credential / internal / audio-video kinds and the changed withheld files denied) on macOS only — Linux
39
+ * and Docker seats get the bundle only (a live read-only bind cannot hide secrets created later).
40
+ */
41
+ reviewGrants?: {
42
+ bundleDir: string;
43
+ maskedOriginal?: {
44
+ dir: string;
45
+ changedWithheld: readonly string[];
46
+ };
47
+ };
35
48
  /**
36
49
  * Where the broker actually listens on the HOST (e.g.
37
50
  * `127.0.0.1:<ephemeral>`).
@@ -60,6 +73,15 @@ export interface SubstrateSpec {
60
73
  * further.
61
74
  */
62
75
  sanitizedEnv: Readonly<Record<string, string>>;
76
+ /**
77
+ * P63 §5.1 (Linux) — a spawn folder the caller already created by exact path (`<copy location>/<task>/spawn/<spawn>/`
78
+ * or `<copy location>/advisory-<spawn>/`) and seeded (agy's token copies in `home/`). The bubblewrap tier adopts it:
79
+ * adds `tmp/`, records it (§6.2) and removes the whole folder at teardown. Absent → the tier creates its own.
80
+ */
81
+ spawnFolder?: {
82
+ dir: string;
83
+ spawnId: string;
84
+ };
63
85
  /**
64
86
  * CP-7 PART D (§3.3 agy-R3 bootstrap) — OPTIONAL, additive. The host
65
87
  * directory holding the agent's `apiKeyHelper` script + the broker-token
@@ -139,6 +161,23 @@ export interface SubstrateSpec {
139
161
  * Default true (writes permitted to workdir).
140
162
  */
141
163
  writableWorkdir?: boolean;
164
+ /**
165
+ * P63 Slice 2 (§8.2) — the spawn's capture directory (`<task folder>/capture/<spawn>/`): granted READ-WRITE beside a
166
+ * read-only view so a plan-mode Codex run can still write its `--output-last-message` file. Seatbelt: an extra write
167
+ * dir; bubblewrap: `--bind` at the same path; Docker: mounted at `/codevibe-capture`. Task-owned, so it outlives the
168
+ * substrate teardown until the plan is read.
169
+ */
170
+ captureDir?: string;
171
+ /**
172
+ * WORKSPACE-HARDENING-DESIGN §6.1 / §6.2 — the user's real project root and CodeVibe's copy-location roots. The
173
+ * Linux tier grants toolchains by the §6.1 rule (never covering these), lays a `--tmpfs` hide over each one that lies
174
+ * inside a bound folder (a project under `/opt`), and binds this task's own folders again after the hides. Absent →
175
+ * no toolchain grants and no hides (the legacy launch).
176
+ */
177
+ workspaceRoots?: {
178
+ project: string;
179
+ copyRoots: readonly string[];
180
+ };
142
181
  }
143
182
  /**
144
183
  * A structural `ChildProcess` subset. The LE widens its internal child
@@ -240,7 +279,13 @@ export interface CommandResult {
240
279
  * `sandbox-exec <argv>` — structurally a {@link SubstrateProc} (so the LE's
241
280
  * `wireLifecycle` consumes it) plus nothing more.
242
281
  */
243
- export type RunnerProc = SubstrateProc;
282
+ export type RunnerProc = SubstrateProc & {
283
+ /**
284
+ * §6.2 broker tier — the desktop's end of the connected AF_UNIX stream socketpair the child holds as fd 3 (Node's
285
+ * `stdio: 'pipe'` on Unix; never an IPC channel). Present only when `spawnLong` was asked for `relayChannel`.
286
+ */
287
+ relayChannel?: import('node:stream').Duplex;
288
+ };
244
289
  /**
245
290
  * The injected process layer for the A1/A5 impls (§3.1). `run` is the
246
291
  * await-to-completion form (network create/rm, container run/kill — the impl
@@ -288,5 +333,7 @@ export interface CommandRunner {
288
333
  launchPathAuthorities?: readonly WorkspaceRootAuthority[];
289
334
  /** P46b ID-31 — the descendant sweep the process host runs if the desktop dies first. */
290
335
  descendantReap?: HostDescendantReap;
336
+ /** §6.2 broker tier — give the child a fourth stdio entry: the relay channel socketpair as its fd 3. */
337
+ relayChannel?: boolean;
291
338
  }): RunnerProc;
292
339
  }
@@ -3,7 +3,7 @@ import type { BrokerDeps } from "../credential-broker/broker";
3
3
  import type { ModelGatewayBroker, UpstreamClient, VendorProvider } from "../credential-broker/types";
4
4
  import type { ProcessRole } from "../local-executor/types";
5
5
  import type { WorkspaceRootAuthority } from "../local-executor/workspace-authority";
6
- import type { SubstrateHandle } from "../substrate/types";
6
+ import type { SubstrateHandle, SubstrateSpec } from "../substrate/types";
7
7
  import { type FallbackLadderDeps } from "./fallback-ladder";
8
8
  /** Map the implementor agent to the broker provider (Claude+Codex only, §6 Q4). */
9
9
  export declare function providerForAgent(agentKind: "CLAUDE" | "GEMINI" | "CODEX" | "ANTIGRAVITY"): VendorProvider | null;
@@ -24,6 +24,13 @@ export interface EngageSubstrateInput {
24
24
  workdir: string;
25
25
  /** Exact workdir incarnation for task-scoped mutable dispatch. */
26
26
  workdirAuthority?: WorkspaceRootAuthority;
27
+ /**
28
+ * P63 Slice 2 (§8.2) — a reviewer seat's read-only grants (the review bundle; macOS: the masked original). `workdir`
29
+ * is then the seat's private scratch. Passed through unchanged to every backend's {@link SubstrateSpec}.
30
+ */
31
+ reviewGrants?: SubstrateSpec['reviewGrants'];
32
+ /** P63 Slice 2 (§8.2) — the spawn's task-owned capture dir, granted read-write beside a read-only view. */
33
+ captureDir?: string;
27
34
  /** The implementor role marker value (always 'implementor' here). */
28
35
  role: ProcessRole;
29
36
  /**
@@ -48,6 +55,13 @@ export interface EngageSubstrateInput {
48
55
  writableWorkdir?: boolean;
49
56
  /** Optional spawn identifier for per-spawn private homes / filters. */
50
57
  spawnId?: string;
58
+ /** §5.1 / §6.2 — where the spawn's start record goes (default `~/.codevibe/shadow-state-v2`); tests isolate it. */
59
+ spawnStateRoot?: string;
60
+ /**
61
+ * P63 §6.1 / §6.2 — the user's real project folder the spawn's copy (or review bundle) was made from. Required to
62
+ * engage the Linux bubblewrap tier: the grant rule hides it and never grants a root that contains it.
63
+ */
64
+ projectRoot?: string;
51
65
  /** Optional home dir override (injectable for tests). */
52
66
  homedir?: string;
53
67
  /** F-P46a-4 — the implementor spawn's process-tree id (descendant reaping marker). */
@@ -102,7 +116,7 @@ export interface EngageSubstrateInput {
102
116
  */
103
117
  export interface SubstrateEngaged {
104
118
  mode: "substrate";
105
- tier: "docker" | "sandbox_exec";
119
+ tier: "docker" | "sandbox_exec" | "bubblewrap";
106
120
  /** Reduced-trust badge for the A5 coarse tier (false for A1 strict). */
107
121
  reducedTrust: boolean;
108
122
  reducedTrustReason?: string;