flowviant 0.102.2 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +87 -474
- package/bin/lib/admission.mjs +19 -3
- package/bin/lib/agentPlan.mjs +2 -2
- package/bin/lib/agentTaskKinds.mjs +93 -0
- package/bin/lib/agySessions.mjs +158 -0
- package/bin/lib/artifactPolicy.mjs +44 -0
- package/bin/lib/artifactPreview.mjs +52 -0
- package/bin/lib/artifactReporter.mjs +163 -0
- package/bin/lib/artifacts.mjs +47 -227
- package/bin/lib/authReject.mjs +16 -4
- package/bin/lib/authproxy.mjs +47 -304
- package/bin/lib/boxIdentity.mjs +245 -0
- package/bin/lib/childEnv.mjs +11 -9
- package/bin/lib/claude.mjs +14 -851
- package/bin/lib/claudeAuth.mjs +3 -3
- package/bin/lib/claudePosture.mjs +367 -0
- package/bin/lib/claudeSessions.mjs +411 -0
- package/bin/lib/claudeStream.mjs +207 -0
- package/bin/lib/cliLimit.mjs +34 -0
- package/bin/lib/commandSpecs.mjs +173 -0
- package/bin/lib/config.mjs +11 -42
- package/bin/lib/credentialDate.mjs +22 -0
- package/bin/lib/credentials.mjs +21 -26
- package/bin/lib/daemonLogging.mjs +55 -0
- package/bin/lib/daemonState.mjs +64 -0
- package/bin/lib/deploy.mjs +148 -647
- package/bin/lib/deployCheckout.mjs +265 -0
- package/bin/lib/deployConfig.mjs +142 -0
- package/bin/lib/deployRunner.mjs +311 -0
- package/bin/lib/deployWire.mjs +41 -0
- package/bin/lib/desktopContract.mjs +56 -105
- package/bin/lib/env.mjs +49 -484
- package/bin/lib/fleet.mjs +184 -2396
- package/bin/lib/fleetJobs.mjs +120 -0
- package/bin/lib/fleetPost.mjs +40 -0
- package/bin/lib/fleetReports.mjs +392 -0
- package/bin/lib/fleetRoster.mjs +470 -0
- package/bin/lib/fleetStartup.mjs +196 -0
- package/bin/lib/fleetWake.mjs +45 -0
- package/bin/lib/fleetWire.mjs +46 -0
- package/bin/lib/git.mjs +34 -3
- package/bin/lib/gitMergeIdentity.mjs +51 -0
- package/bin/lib/grant.mjs +5 -3
- package/bin/lib/help.mjs +3 -125
- package/bin/lib/holder.mjs +482 -0
- package/bin/lib/instance.mjs +124 -46
- package/bin/lib/knowledge.mjs +16 -799
- package/bin/lib/knowledgeFetch.mjs +38 -0
- package/bin/lib/knowledgeFileSync.mjs +112 -0
- package/bin/lib/knowledgeFiles.mjs +487 -0
- package/bin/lib/knowledgeLibrary.mjs +249 -0
- package/bin/lib/landed.mjs +2 -1
- package/bin/lib/listeners.mjs +99 -141
- package/bin/lib/localSessions.mjs +30 -555
- package/bin/lib/login.mjs +3 -2
- package/bin/lib/loginPath.mjs +3 -5
- package/bin/lib/machineDisconnect.mjs +203 -0
- package/bin/lib/machineEnv.mjs +22 -0
- package/bin/lib/machineLimits.mjs +61 -0
- package/bin/lib/machineListing.mjs +338 -0
- package/bin/lib/machines.mjs +78 -455
- package/bin/lib/machinesCommand.mjs +214 -0
- package/bin/lib/mcp-cli.mjs +2 -1
- package/bin/lib/patchRevert.mjs +60 -0
- package/bin/lib/prWorkflow.mjs +157 -0
- package/bin/lib/preflight.mjs +2 -1
- package/bin/lib/pressurePolicy.mjs +104 -0
- package/bin/lib/preview.mjs +2 -272
- package/bin/lib/previewFramePolicy.mjs +112 -0
- package/bin/lib/previewGatePolicy.mjs +236 -0
- package/bin/lib/previewRegistry.mjs +235 -0
- package/bin/lib/printable.mjs +2 -2
- package/bin/lib/procRegistry.mjs +28 -18
- package/bin/lib/procScan.mjs +146 -0
- package/bin/lib/processes.mjs +29 -37
- package/bin/lib/projectToolRuntimes.mjs +138 -0
- package/bin/lib/projectTools.mjs +13 -88
- package/bin/lib/prompts.mjs +28 -42
- package/bin/lib/repoPath.mjs +26 -0
- package/bin/lib/resources.mjs +7 -69
- package/bin/lib/resumeLost.mjs +107 -0
- package/bin/lib/runTurn.mjs +324 -0
- package/bin/lib/runtimeAntigravity.mjs +230 -0
- package/bin/lib/runtimeCapabilities.mjs +334 -0
- package/bin/lib/runtimeClaude.mjs +126 -0
- package/bin/lib/runtimeCodex.mjs +217 -0
- package/bin/lib/runtimeDetection.mjs +121 -0
- package/bin/lib/runtimeEvents.mjs +482 -0
- package/bin/lib/runtimeOfferings.mjs +4 -1
- package/bin/lib/runtimes.mjs +42 -1402
- package/bin/lib/safeFileName.mjs +74 -0
- package/bin/lib/shipMerge.mjs +33 -2
- package/bin/lib/standDownExit.mjs +181 -0
- package/bin/lib/standDownGate.mjs +48 -0
- package/bin/lib/startProject.mjs +279 -0
- package/bin/lib/trace.mjs +3 -3
- package/bin/lib/turnLock.mjs +68 -0
- package/bin/lib/turnProfile.mjs +77 -0
- package/bin/lib/uninstall.mjs +55 -45
- package/bin/lib/uninstallCopies.mjs +112 -0
- package/bin/lib/update.mjs +30 -17
- package/bin/lib/uplinkScrub.mjs +265 -0
- package/bin/lib/vault.mjs +22 -151
- package/bin/lib/vaultDiff.mjs +183 -0
- package/bin/lib/versionOrder.mjs +42 -0
- package/bin/lib/views.mjs +35 -12
- package/bin/lib/wikiRunner.mjs +630 -0
- package/bin/lib/work.mjs +188 -3037
- package/bin/lib/workAdoptCarry.mjs +94 -0
- package/bin/lib/workAgentCheck.mjs +278 -0
- package/bin/lib/workAgentMerges.mjs +54 -142
- package/bin/lib/workAgentPlans.mjs +14 -12
- package/bin/lib/workAgentPrecheck.mjs +393 -0
- package/bin/lib/workAgentPublish.mjs +177 -0
- package/bin/lib/workAgentReview.mjs +8 -666
- package/bin/lib/workAgentTurnBegun.mjs +109 -0
- package/bin/lib/workAgentTurnExecution.mjs +604 -0
- package/bin/lib/workAgentTurnOutcome.mjs +207 -0
- package/bin/lib/workAgentTurnReports.mjs +189 -0
- package/bin/lib/workAgentTurns.mjs +92 -897
- package/bin/lib/workAttachments.mjs +93 -0
- package/bin/lib/workBrain.mjs +53 -0
- package/bin/lib/workChildren.mjs +110 -0
- package/bin/lib/workCommandAudit.mjs +62 -0
- package/bin/lib/workDiffs.mjs +2 -1
- package/bin/lib/workNarration.mjs +152 -0
- package/bin/lib/workPlaceReports.mjs +426 -0
- package/bin/lib/workPlaces.mjs +228 -0
- package/bin/lib/workPreviews.mjs +31 -9
- package/bin/lib/workProcesses.mjs +4 -5
- package/bin/lib/workPullRequests.mjs +52 -126
- package/bin/lib/workReportQueue.mjs +254 -0
- package/bin/lib/workRetire.mjs +185 -0
- package/bin/lib/workSessionGroups.mjs +166 -0
- package/bin/lib/workSessionRuntime.mjs +164 -0
- package/bin/lib/workSessionTokens.mjs +68 -0
- package/bin/lib/workSessionTurns.mjs +892 -0
- package/bin/lib/workShip.mjs +9 -29
- package/bin/lib/workToolLog.mjs +76 -0
- package/package.json +3 -2
- package/bin/lib/patch.mjs +0 -363
package/bin/lib/claudeAuth.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Two ways they diverge, and this module reports both:
|
|
18
18
|
*
|
|
19
|
-
* · THE ENVIRONMENT. `
|
|
19
|
+
* · THE ENVIRONMENT. `runTurn.mjs` spawns the CLI with `{...process.env}` on
|
|
20
20
|
* purpose — its header says so: "the CLI's own credentials live in this
|
|
21
21
|
* environment, and handing it a curated one signs it out". It also stopped
|
|
22
22
|
* deleting ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN deliberately, because on
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* ── WHAT THIS IS NOT ──
|
|
34
34
|
*
|
|
35
35
|
* NOT A POLICY. Flowviant does not pick a credential, does not prefer the
|
|
36
|
-
* subscription over a key, and does not call either one wrong — `
|
|
36
|
+
* subscription over a key, and does not call either one wrong — `runTurn.mjs`
|
|
37
37
|
* settled that: "Which credential is correct, and whether an account may be
|
|
38
38
|
* shared, is between the operator and the vendor." This reports what is
|
|
39
39
|
* PRESENT so a person can see the divergence; it never resolves it.
|
|
@@ -62,7 +62,7 @@ import { join } from 'node:path';
|
|
|
62
62
|
/**
|
|
63
63
|
* The variables that can carry auth into the CLI from the environment.
|
|
64
64
|
*
|
|
65
|
-
* The first two are the pair `
|
|
65
|
+
* The first two are the pair `runTurn.mjs` documents itself as deliberately NOT
|
|
66
66
|
* deleting, and both appear in childEnv.mjs's allowlist. The third is Claude
|
|
67
67
|
* Code's own headless/CI token. Names only ever leave this module — a NAME is
|
|
68
68
|
* what a person greps for, and it is not a secret.
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE'S PERMISSION POSTURES — the `--allowedTools` / `--disallowedTools`
|
|
3
|
+
* lists (and the read guard's `--settings`) that ARE each turn profile on
|
|
4
|
+
* Claude Code, looked up by the profile's name (`claudePermFor`).
|
|
5
|
+
*
|
|
6
|
+
* Split out of claude.mjs (SOLID 2026-09-26) because a posture changes for one
|
|
7
|
+
* reason — a measured fact about what the CLI admits under a list — and that
|
|
8
|
+
* reason has nothing to do with how a turn is spawned or how its event stream
|
|
9
|
+
* is read. Every list here was probed on a real CLI; the probes are recorded
|
|
10
|
+
* beside the list they justify, and they move with it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { SAFE } from './config.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* THE READ GUARD, ON EVERY CURATED POSTURE (2026-09-23) — see
|
|
18
|
+
* hooks/readGuard.mjs for what it refuses and the probe that proved it fires.
|
|
19
|
+
*
|
|
20
|
+
* A `Bash(git log:*)` allow is a PREFIX: it names the program and says nothing
|
|
21
|
+
* about the arguments, and `git log --output=<path>` writes any file while
|
|
22
|
+
* `-c core.fsmonitor=…` / `--ext-diff` run a program. A reviewer landed
|
|
23
|
+
* `git log -1 --format='tformat:x' --output=pwned.txt` under RESEARCH_PERM with
|
|
24
|
+
* no denial, and it re-measured the same way here. So every list below that
|
|
25
|
+
* promises "reads, and nothing else" carries a `PreToolUse` hook through
|
|
26
|
+
* `--settings`, measured on Claude Code 2.1.281: it fires under
|
|
27
|
+
* `--allowedTools`, exit 2 blocks the call, and its sentence reaches the model.
|
|
28
|
+
*
|
|
29
|
+
* NEVER ON THE BUILD POSTURE: `--dangerously-skip-permissions` is the
|
|
30
|
+
* operator's own choice for a turn that is meant to write, and a guard there
|
|
31
|
+
* would be refusing the job. Nor on PLAN_MODE_PERM, which is the CLI's own
|
|
32
|
+
* posture on the owner's own tab, not a list this file promises anything about.
|
|
33
|
+
*
|
|
34
|
+
* The hook runs as `<this node> <readGuard.mjs>` — the daemon's own
|
|
35
|
+
* `process.execPath`, never a `node` looked up on a PATH the operator may not
|
|
36
|
+
* have, because a hook that fails to START is a non-blocking error and the
|
|
37
|
+
* command runs anyway.
|
|
38
|
+
*/
|
|
39
|
+
export const READ_GUARD_PATH = fileURLToPath(new URL('./hooks/readGuard.mjs', import.meta.url));
|
|
40
|
+
const hookQuote = (s) =>
|
|
41
|
+
process.platform === 'win32' ? `"${s}"` : `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
42
|
+
export const READ_GUARD_SETTINGS = JSON.stringify({
|
|
43
|
+
hooks: {
|
|
44
|
+
PreToolUse: [
|
|
45
|
+
{
|
|
46
|
+
matcher: 'Bash',
|
|
47
|
+
hooks: [{ type: 'command', command: `${hookQuote(process.execPath)} ${hookQuote(READ_GUARD_PATH)}` }],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
/** Spread FIRST into a curated list: `--allowedTools` is variadic, so the
|
|
53
|
+
* `--settings` pair must stand before it or be swallowed as a tool name. */
|
|
54
|
+
const READ_GUARD = ['--settings', READ_GUARD_SETTINGS];
|
|
55
|
+
|
|
56
|
+
/** A path a permission rule can name: absolute POSIX, and free of the glob and
|
|
57
|
+
* rule-syntax characters that would change what the rule means. Anything else
|
|
58
|
+
* gets no rule — `--add-dir` still admits the directory (measured). */
|
|
59
|
+
const ruleSafeAbsolute = (p) =>
|
|
60
|
+
typeof p === 'string' && p.startsWith('/') && !/[*?[\]{}()\\\n]/.test(p) ? p.replace(/\/+$/, '') : null;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* THE READ FENCE — the worktree, plus the knowledge library by its absolute
|
|
64
|
+
* path, and nothing else on the box (2026-09-24, the audit).
|
|
65
|
+
*
|
|
66
|
+
* Every curated posture used to allow bare `Read`, `Grep` and `Glob`, and a
|
|
67
|
+
* bare allow is not "read the repo": measured on Claude Code 2.1.281, bare
|
|
68
|
+
* `Read` read a file outside the working directory and bare `Grep` searched
|
|
69
|
+
* one. So a design, consult or capture turn — each steered by words anyone who
|
|
70
|
+
* can file a card can write — could read `~/.flowviant/credentials.json`, which
|
|
71
|
+
* holds the MACHINE CREDENTIAL of every project connected on this box, and put
|
|
72
|
+
* it in an artifact, a plan note or a transcript that leaves the machine. That
|
|
73
|
+
* is a cross-project leak from a posture this file calls read-only.
|
|
74
|
+
*
|
|
75
|
+
* The fence is research's, measured the same day in the design shape:
|
|
76
|
+
* `Read(./**)` and `Glob(./**)` read the worktree; the knowledge library reads
|
|
77
|
+
* through `--add-dir` plus its own `//` rule; a file outside both was refused
|
|
78
|
+
* ("Claude requested permissions to read from …, but you haven't granted it
|
|
79
|
+
* yet"). GREP CARRIES NO ALLOW RULE AT ALL, on purpose: with none, Grep in the
|
|
80
|
+
* working directory (and in the added knowledge directory) ran, and Grep of a
|
|
81
|
+
* path outside them was refused. The Bash readers these postures keep are
|
|
82
|
+
* path-validated by the CLI on its own (see CONSULT_BASH).
|
|
83
|
+
*/
|
|
84
|
+
function fencedReads(knowledgeDir) {
|
|
85
|
+
const kd = ruleSafeAbsolute(knowledgeDir);
|
|
86
|
+
return [
|
|
87
|
+
'Read(./**)',
|
|
88
|
+
...(kd ? [`Read(/${kd}/**)`] : []),
|
|
89
|
+
'Glob(./**)',
|
|
90
|
+
...(kd ? [`Glob(/${kd}/**)`] : []),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The shell readers CONSULT and PLAN keep. Each one is PATH-VALIDATED by the
|
|
95
|
+
* CLI itself — measured on 2.1.281 (2026-09-24): with `Bash(cat:*)`,
|
|
96
|
+
* `Bash(head:*)` and `Bash(ls:*)` allowed, `cat`/`ls`/`head` on a file outside
|
|
97
|
+
* the working directories were each refused ("Claude Code may only
|
|
98
|
+
* concatenate files from the allowed working directories") — so it is the
|
|
99
|
+
* Read/Grep/Glob TOOLS, not these, that needed the fence above. */
|
|
100
|
+
const CONSULT_BASH = [
|
|
101
|
+
'Bash(ls:*)',
|
|
102
|
+
'Bash(wc:*)',
|
|
103
|
+
'Bash(head:*)',
|
|
104
|
+
'Bash(cat:*)',
|
|
105
|
+
'Bash(git log:*)',
|
|
106
|
+
'Bash(git show:*)',
|
|
107
|
+
'Bash(git diff:*)',
|
|
108
|
+
'Bash(git rev-parse:*)',
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* PLAN — read the repo, write the plan, never the code.
|
|
113
|
+
*
|
|
114
|
+
* The read half is `consultPermFor`'s verbatim (the read fence and the
|
|
115
|
+
* path-validated shell readers): this turn's prompt is steered by
|
|
116
|
+
* anything a project editor can type, so the same threat applies and the same
|
|
117
|
+
* allowlist answers it. What is added is the control plane and NOTHING else —
|
|
118
|
+
* `mcp__flowviant` is the plan principal's token, whose entire tool set is the
|
|
119
|
+
* five plan tools (the server refuses anything else on it). So even a fully
|
|
120
|
+
* hijacked turn's most destructive reachable act is dropping a slice from the
|
|
121
|
+
* plan it is already in, which a human can see and undo in the thread.
|
|
122
|
+
*
|
|
123
|
+
* Note what is absent versus WIKI_PERM: Write, Edit, mkdir and rm. The
|
|
124
|
+
* cartographer needs those because it authors files; a planner authors records
|
|
125
|
+
* through an API, and there is no file on this machine it has any business
|
|
126
|
+
* touching.
|
|
127
|
+
*/
|
|
128
|
+
export function planPermFor(knowledgeDir) {
|
|
129
|
+
return [
|
|
130
|
+
...READ_GUARD,
|
|
131
|
+
'--allowedTools',
|
|
132
|
+
'mcp__flowviant',
|
|
133
|
+
...fencedReads(knowledgeDir),
|
|
134
|
+
...CONSULT_BASH,
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Unattended (default) skips prompts so the agent never stalls with no terminal;
|
|
139
|
+
// FLOWVIANT_SAFE=1 restricts to a curated toolset instead.
|
|
140
|
+
const PERM = SAFE
|
|
141
|
+
? [
|
|
142
|
+
'--allowedTools',
|
|
143
|
+
'mcp__flowviant',
|
|
144
|
+
'Edit',
|
|
145
|
+
'Write',
|
|
146
|
+
'Read',
|
|
147
|
+
'Grep',
|
|
148
|
+
'Glob',
|
|
149
|
+
'Bash(git:*)',
|
|
150
|
+
'Bash(gh:*)',
|
|
151
|
+
'Bash(npm:*)',
|
|
152
|
+
'Bash(bun:*)',
|
|
153
|
+
]
|
|
154
|
+
: ['--dangerously-skip-permissions'];
|
|
155
|
+
|
|
156
|
+
// Wiki turns are read-the-repo + write-the-vault ONLY — always curated, never
|
|
157
|
+
// --dangerously-skip-permissions: no gh, no push-capable git, no package
|
|
158
|
+
// managers, and nothing that can EXECUTE arbitrary commands — no `find`
|
|
159
|
+
// (-exec/-delete) and no `git grep` (-O<pager> runs a shell; the Grep tool
|
|
160
|
+
// covers search). Command execution is the line: it enables network exfil,
|
|
161
|
+
// which plain file writes never do. `rm` IS allowed: pruning a stale vault
|
|
162
|
+
// page requires a real file deletion (that's how the sync protocol learns of
|
|
163
|
+
// it), and the blast radius is bounded — the daemon resets the repo worktree
|
|
164
|
+
// after every wiki turn, and the vault has its own git history.
|
|
165
|
+
// (Write/Edit can't be path-scoped here; the worktree reset is the backstop.)
|
|
166
|
+
const WIKI_PERM = [
|
|
167
|
+
...READ_GUARD,
|
|
168
|
+
'--allowedTools',
|
|
169
|
+
'Read',
|
|
170
|
+
'Grep',
|
|
171
|
+
'Glob',
|
|
172
|
+
'Edit',
|
|
173
|
+
'Write',
|
|
174
|
+
'Bash(ls:*)',
|
|
175
|
+
'Bash(wc:*)',
|
|
176
|
+
'Bash(head:*)',
|
|
177
|
+
'Bash(cat:*)',
|
|
178
|
+
'Bash(mkdir:*)',
|
|
179
|
+
'Bash(rm:*)',
|
|
180
|
+
'Bash(git status:*)',
|
|
181
|
+
'Bash(git log:*)',
|
|
182
|
+
'Bash(git show:*)',
|
|
183
|
+
'Bash(git diff:*)',
|
|
184
|
+
'Bash(git rev-parse:*)',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
// A CONSULT reads and answers. Nothing else.
|
|
188
|
+
//
|
|
189
|
+
// It used to run on WIKI_PERM, whose comment two blocks up says the quiet part:
|
|
190
|
+
// Write/Edit "can't be path-scoped here; the worktree reset is the backstop".
|
|
191
|
+
// That is a fine trade for the cartographer, which exists to author files and
|
|
192
|
+
// gets reset after every turn. It is the wrong trade for a consult, whose prompt
|
|
193
|
+
// is steered by a question ANY project editor can write and which had no reset
|
|
194
|
+
// behind it — so a sentence in a chat box could reach Write, rm and mkdir on
|
|
195
|
+
// someone else's machine. The permission list is the enforcement; the prompt's
|
|
196
|
+
// "do not change anything" is only an instruction, and instructions are exactly
|
|
197
|
+
// what an injected question competes with.
|
|
198
|
+
export function consultPermFor(knowledgeDir) {
|
|
199
|
+
return [...READ_GUARD, '--allowedTools', ...fencedReads(knowledgeDir), ...CONSULT_BASH];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* THE TWO NON-CODE POSTURES (0.97.0) — a design card and a research card may
|
|
204
|
+
* WRITE, and only under `.flowviant/artifacts/`.
|
|
205
|
+
*
|
|
206
|
+
* The card's words are steered by anyone who can file a card, so "change
|
|
207
|
+
* nothing" as prose is only an instruction, and the permission list is the
|
|
208
|
+
* enforcement. What each carries is the ONE write its contract needs:
|
|
209
|
+
*
|
|
210
|
+
* · `Edit(.flowviant/artifacts/**)` — PROBED on Claude Code 2.1.281 before
|
|
211
|
+
* relying on it (2026-09-23): with `--allowedTools 'Read'
|
|
212
|
+
* 'Edit(.flowviant/artifacts/**)'`, a `-p` turn told to write
|
|
213
|
+
* `.flowviant/artifacts/a.html` AND `b.txt` at the root wrote the first and
|
|
214
|
+
* was DENIED the second. The Edit rule governs every file-writing tool,
|
|
215
|
+
* Write included. Spelled `Write(.flowviant/artifacts/**)` — or `./`- or
|
|
216
|
+
* `//abs`-anchored — the same probe was denied BOTH files, so that spelling
|
|
217
|
+
* is not a scoped write at all, it is no write. Relative to the turn's cwd,
|
|
218
|
+
* which is the agent's own worktree, which is where the artifact scan looks.
|
|
219
|
+
* `**` AND NOT `*`, measured the same day: `Edit(.flowviant/artifacts/*)`
|
|
220
|
+
* let a Write to `.flowviant/artifacts/sub/b.html` through exactly as `**`
|
|
221
|
+
* did, so the single-level spelling is not narrower on this CLI and would
|
|
222
|
+
* only read as if it were. The scan keeps top-level files, so the
|
|
223
|
+
* contracts say "no subfolders" — the prompt is the only thing that can.
|
|
224
|
+
*
|
|
225
|
+
* THEY ARE NOT THE SAME READER, and the difference is the network:
|
|
226
|
+
*
|
|
227
|
+
* · DESIGN draws THIS product, so it reads the repo — the worktree and the
|
|
228
|
+
* knowledge library through the read fence (`fencedReads`, 2026-09-24:
|
|
229
|
+
* it was bare Read/Grep/Glob, which reach the whole box), Grep with no
|
|
230
|
+
* allow rule, `ls`, and `.env*` denied by name — and has no web at all (a mockup that went looking on the
|
|
231
|
+
* web would be a mockup of somebody else's product). No git readers and no
|
|
232
|
+
* cat/head/wc: Read covers every one of them, and each git reader was an
|
|
233
|
+
* `--output` away from a write (see READ_GUARD, which it still carries for
|
|
234
|
+
* the `ls` it keeps).
|
|
235
|
+
* · RESEARCH has the web — WebSearch and WebFetch — and a turn that can both
|
|
236
|
+
* read a secret and fetch a URL can send one. So its reading is FENCED to
|
|
237
|
+
* what the card is about: `Read(./**)` (the worktree) plus the project's
|
|
238
|
+
* knowledge library by its absolute path, `Glob(./**)` for names, NO Grep
|
|
239
|
+
* (an unscoped search of the home directory prints matching lines), NO
|
|
240
|
+
* Bash at all, and `.env*` DENIED by name. PROBED on 2.1.281, the whole
|
|
241
|
+
* list with `--add-dir <knowledge>`: a file in the cwd read; `/etc/hostname`,
|
|
242
|
+
* a sibling directory's file and `~/.flowviant/credentials.json` were each
|
|
243
|
+
* refused ("Claude requested permissions to read from …, but you haven't
|
|
244
|
+
* granted it yet"); `.env` was refused ("File is in a directory that is
|
|
245
|
+
* denied by your permission settings"); the knowledge file read. Two
|
|
246
|
+
* controls: WITHOUT the `--disallowedTools` pair, `Read(./**)` read `.env`
|
|
247
|
+
* — the deny is load-bearing; and a bare `Glob` listed `~/.flowviant`
|
|
248
|
+
* while `Glob(./**)` refused it and still listed the cwd and the added
|
|
249
|
+
* directory. The knowledge `Read(//…)` rule is belt: `--add-dir` alone
|
|
250
|
+
* admitted the file, and the rule keeps the read allowed should the
|
|
251
|
+
* adapter ever stop passing that flag.
|
|
252
|
+
* AND `Bash` IS DENIED BY NAME, because leaving it off the allow list is
|
|
253
|
+
* not "no Bash": measured the same day, with Bash absent from
|
|
254
|
+
* `--allowedTools` the CLI still ran `git log -1 --format=%s` and `ls` on
|
|
255
|
+
* its own read-only classifier (it did refuse `git log … --output=…`,
|
|
256
|
+
* "This command requires approval" — the reviewer's write needed the
|
|
257
|
+
* explicit `Bash(git log:*)` prefix the old list carried). With `Bash` in
|
|
258
|
+
* `--disallowedTools` the tool was not there at all.
|
|
259
|
+
*
|
|
260
|
+
* NO MCP, and no shell that could commit: the agent turn has no MCP anyway
|
|
261
|
+
* (see SYSTEM_AGENT's header), and a posture that could run `git commit` could
|
|
262
|
+
* commit the mockup it was told to keep out of git.
|
|
263
|
+
*
|
|
264
|
+
* STATED, not closed: research may still read any file in the worktree and
|
|
265
|
+
* name it in a WebFetch URL — the repository is what a research card is about,
|
|
266
|
+
* and a turn that could not read it could not answer. Design may read any
|
|
267
|
+
* file in its worktree and put it in an artifact the server stores; text
|
|
268
|
+
* artifacts are scrubbed of the machine's known secret values — and of any
|
|
269
|
+
* Flowviant credential, by shape — on the way out (artifacts.mjs, uplinkScrub.mjs), and
|
|
270
|
+
* binary ones are withheld when they carry one.
|
|
271
|
+
* Design still runs a plain `git log` on the CLI's own read-only classifier
|
|
272
|
+
* though no git reader is allowed (measured) — the guard is what refuses its
|
|
273
|
+
* `--output`. And the Agent tool is offered with no allow rule on this CLI:
|
|
274
|
+
* a research turn with Bash denied launched a subagent to run one, and the
|
|
275
|
+
* subagent had no Bash either (measured) — the rules are inherited. Whether
|
|
276
|
+
* the guard's hook sees a subagent's own calls was not measured here.
|
|
277
|
+
*/
|
|
278
|
+
const ARTIFACT_WRITE = 'Edit(.flowviant/artifacts/**)';
|
|
279
|
+
/**
|
|
280
|
+
* PLAN MODE (0.97.0) — a Workbench tab's `planMode` switch, and Claude Code's
|
|
281
|
+
* own `--permission-mode plan` INSTEAD OF every list above: the CLI reads,
|
|
282
|
+
* decides and answers with a plan, and refuses each write itself.
|
|
283
|
+
*
|
|
284
|
+
* PROBED on Claude Code 2.1.281 before relying on it (2026-09-23), in a scratch
|
|
285
|
+
* git repo, `-p … --output-format stream-json --verbose`:
|
|
286
|
+
*
|
|
287
|
+
* · ALONE, asked to "add a subtract(a, b) function to app.js": the init event
|
|
288
|
+
* reads `permissionMode: "plan"`, the turn Read the file, ran a read-only
|
|
289
|
+
* `find`, wrote its plan to `~/.claude/plans/<slug>.md` (the CLI's own plan
|
|
290
|
+
* file, outside the repo), tried `ExitPlanMode` and was told "Error: No such
|
|
291
|
+
* tool available: ExitPlanMode. ExitPlanMode is disabled for this session",
|
|
292
|
+
* and ended with the plan as its result text. app.js was untouched.
|
|
293
|
+
* · BESIDE `--dangerously-skip-permissions`, same ask: the init event reads
|
|
294
|
+
* `permissionMode: "bypassPermissions"` — no error, no warning — and the
|
|
295
|
+
* edit LANDED. The bypass silently wins. So this list is a REPLACEMENT for
|
|
296
|
+
* `perm`, never an addition, and a plan turn can never carry both.
|
|
297
|
+
* · WITH `--mcp-config` and `--allowedTools mcp__flowviant`: the server
|
|
298
|
+
* connected and its tools were listed, but a call was refused — "Cannot
|
|
299
|
+
* call mcp__flowviant__log_work while in plan mode." — and without the
|
|
300
|
+
* `--allowedTools` entry, "Claude requested permissions to use
|
|
301
|
+
* mcp__flowviant__log_work, but you haven't granted it yet." Plan mode
|
|
302
|
+
* admitted the call ONLY when the server annotated the tool
|
|
303
|
+
* `readOnlyHint: true`, and none of the session tools (stream_session_turn,
|
|
304
|
+
* log_work, file_card …) are read-only or annotated so. So a plan turn runs
|
|
305
|
+
* PLAIN — no MCP, the plain tab's contract — rather than mounting a control
|
|
306
|
+
* plane whose every call the CLI refuses. See workSessionTurns.mjs.
|
|
307
|
+
*/
|
|
308
|
+
export const PLAN_MODE_PERM = ['--permission-mode', 'plan'];
|
|
309
|
+
export function designPermFor(knowledgeDir) {
|
|
310
|
+
return [
|
|
311
|
+
...READ_GUARD,
|
|
312
|
+
'--allowedTools',
|
|
313
|
+
...fencedReads(knowledgeDir),
|
|
314
|
+
'Bash(ls:*)',
|
|
315
|
+
ARTIFACT_WRITE,
|
|
316
|
+
// A mockup is uploaded and runs scripts in a frame that may navigate
|
|
317
|
+
// itself, so the checkout's own secrets are denied by name too — the same
|
|
318
|
+
// pair research carries, measured load-bearing there.
|
|
319
|
+
'--disallowedTools',
|
|
320
|
+
'Read(./.env*)',
|
|
321
|
+
'Read(./**/.env*)',
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
export const DESIGN_PERM = designPermFor(null);
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* RESEARCH, built at spawn — the knowledge library is the one directory
|
|
328
|
+
* outside the worktree it may read, and only the spawn knows where that is.
|
|
329
|
+
* `//` is the CLI's own spelling for an absolute path in a rule.
|
|
330
|
+
*/
|
|
331
|
+
export function researchPerm(knowledgeDir) {
|
|
332
|
+
return [
|
|
333
|
+
...READ_GUARD,
|
|
334
|
+
'--allowedTools',
|
|
335
|
+
...fencedReads(knowledgeDir),
|
|
336
|
+
'WebSearch',
|
|
337
|
+
'WebFetch',
|
|
338
|
+
ARTIFACT_WRITE,
|
|
339
|
+
'--disallowedTools',
|
|
340
|
+
'Read(./.env*)',
|
|
341
|
+
'Read(./**/.env*)',
|
|
342
|
+
'Bash',
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
/** The list with no library — what a project that keeps none runs under. */
|
|
346
|
+
export const RESEARCH_PERM = researchPerm(null);
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* CLAUDE'S PERMISSION LIST FOR A TURN PROFILE, by the profile's name — one
|
|
350
|
+
* entry per name in turnProfile.mjs (a test holds the two tables equal). The
|
|
351
|
+
* builders that take the knowledge directory get it; the fixed lists ignore it.
|
|
352
|
+
*/
|
|
353
|
+
const CLAUDE_PERM = {
|
|
354
|
+
build: () => PERM,
|
|
355
|
+
wiki: () => WIKI_PERM,
|
|
356
|
+
consult: (kd) => consultPermFor(kd),
|
|
357
|
+
plan: (kd) => planPermFor(kd),
|
|
358
|
+
'plan-mode': () => PLAN_MODE_PERM,
|
|
359
|
+
design: (kd) => designPermFor(kd),
|
|
360
|
+
research: (kd) => researchPerm(kd),
|
|
361
|
+
};
|
|
362
|
+
export const CLAUDE_PERM_PROFILES = Object.freeze(Object.keys(CLAUDE_PERM));
|
|
363
|
+
export function claudePermFor(profileName, knowledgeDir) {
|
|
364
|
+
const build = Object.hasOwn(CLAUDE_PERM, profileName) ? CLAUDE_PERM[profileName] : null;
|
|
365
|
+
if (!build) throw new Error(`no Claude permission list for profile '${profileName}'`);
|
|
366
|
+
return build(knowledgeDir);
|
|
367
|
+
}
|