@indigoai-us/hq-cli 5.311.2 → 5.311.3
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/CHANGELOG.md +4 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +31 -5
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/assets/scaffold/core/scripts/hq-delegate-verify.sh +27 -2
- package/dist/commands/core-delegate-w1b.js +54 -7
- package/dist/commands/core-delegate.d.ts +1 -0
- package/dist/commands/core-delegate.js +1 -1
- package/dist/commands/files-browse.d.ts +3 -2
- package/dist/commands/files-browse.js +2 -7
- package/dist/commands/lanes.js +6 -3
- package/dist/utils/files-browse-acl.d.ts +5 -0
- package/dist/utils/files-browse-acl.js +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.311.3] — 2026-10-01
|
|
6
|
+
- Match company file access checks against the company-anchored paths printed by `hq files browse`.
|
|
7
|
+
- Record a placement resolved to the current host as local so the lane starts without dispatching back through Outpost pickup.
|
|
8
|
+
|
|
5
9
|
## [5.311.2] — 2026-10-01
|
|
6
10
|
- Reduce fast-path startup work by loading network compatibility only on routes that need it.
|
|
7
11
|
- Make envelope `next_check_at` an unconditional keep-alive hold and remove its completed rollout flag.
|
|
@@ -60,13 +60,31 @@ STATUS="$(jq -r '.status // empty' "$MANIFEST")"
|
|
|
60
60
|
|
|
61
61
|
# File-ACL principals must be an email, grp_<id>, or @all — an agentUid is
|
|
62
62
|
# NOT accepted by `hq files share`. For agent recipients, grants flow through
|
|
63
|
-
# a deterministic per-agent delegation group
|
|
64
|
-
# grp_dlg
|
|
63
|
+
# a deterministic per-agent delegation group keyed by the full UID digest:
|
|
64
|
+
# grp_dlg<first 32 SHA-256 hex characters>, containing exactly that agent.
|
|
65
|
+
agent_group_id() {
|
|
66
|
+
local principal="$1" digest
|
|
67
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
68
|
+
digest="$(printf '%s' "$principal" | sha256sum)"
|
|
69
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
70
|
+
digest="$(printf '%s' "$principal" | shasum -a 256)"
|
|
71
|
+
else
|
|
72
|
+
die "sha256 utility is required for agent delegation groups"
|
|
73
|
+
fi
|
|
74
|
+
printf 'grp_dlg%.32s' "$digest"
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
GRANT_PRINCIPAL="$PRINCIPAL"
|
|
66
78
|
case "$PRINCIPAL" in
|
|
67
79
|
agt_*)
|
|
68
80
|
TAIL="$(printf '%s' "$PRINCIPAL" | tail -c 9 | tr '[:upper:]' '[:lower:]')"
|
|
69
|
-
|
|
81
|
+
LEGACY_GROUP="grp_dlg-$TAIL"
|
|
82
|
+
NEXT_GROUP="$(agent_group_id "$PRINCIPAL")"
|
|
83
|
+
RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST")"
|
|
84
|
+
case "$RECORDED_GROUP" in
|
|
85
|
+
"$LEGACY_GROUP"|"$NEXT_GROUP") GRANT_PRINCIPAL="$RECORDED_GROUP" ;;
|
|
86
|
+
*) GRANT_PRINCIPAL="$NEXT_GROUP" ;;
|
|
87
|
+
esac
|
|
70
88
|
;;
|
|
71
89
|
esac
|
|
72
90
|
|
|
@@ -133,12 +151,20 @@ done
|
|
|
133
151
|
# --- 1b. agent recipients: ensure the delegation group exists + contains them
|
|
134
152
|
|
|
135
153
|
if [ "$GRANT_PRINCIPAL" != "$PRINCIPAL" ]; then
|
|
136
|
-
#
|
|
137
|
-
#
|
|
154
|
+
# Group creation and add are idempotent-tolerated; confirm that the complete
|
|
155
|
+
# member list is exactly this agent before any file ACL is shared.
|
|
138
156
|
hq groups create "$GRANT_PRINCIPAL" --name "Delegation: $DISPLAY" --company "$COMPANY" \
|
|
139
157
|
|| echo "hq-delegate-grant: group create reported an error (may already exist) — continuing" >&2
|
|
140
158
|
hq groups add "$GRANT_PRINCIPAL" "$PRINCIPAL" --company "$COMPANY" \
|
|
141
159
|
|| echo "hq-delegate-grant: group add reported an error (may already be a member) — continuing" >&2
|
|
160
|
+
GROUP_MEMBERS="$(NO_COLOR=1 FORCE_COLOR=0 hq groups members "$GRANT_PRINCIPAL" --company "$COMPANY")" \
|
|
161
|
+
|| die "could not verify delegation group membership"
|
|
162
|
+
MEMBER_IDS="$(printf '%s\n' "$GROUP_MEMBERS" | awk '
|
|
163
|
+
{ gsub(/\033\[[0-9;]*m/, "", $0) }
|
|
164
|
+
/^PERSON_UID([[:space:]]|$)/ { collecting = 1; next }
|
|
165
|
+
collecting && NF { print $1 }
|
|
166
|
+
')"
|
|
167
|
+
[ "$MEMBER_IDS" = "$PRINCIPAL" ] || die "delegation group membership does not exactly match recipient"
|
|
142
168
|
fi
|
|
143
169
|
|
|
144
170
|
# --- 2+3. exact structured read-back; never infer identity from a new row ---
|
|
@@ -93,7 +93,7 @@ if [ -f "$PRD_ABS" ] && jq -e . "$PRD_ABS" >/dev/null 2>&1; then
|
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
95
|
GET_LINES="$(jq -r --arg co "$COMPANY" \
|
|
96
|
-
'.vaultPrefixes[] | "hq files get \(.prefix) --company \($co)"' "$MANIFEST")"
|
|
96
|
+
'def bashq: if test("^[A-Za-z0-9_./:@+-]+$") then . else @sh end; .vaultPrefixes[] | "hq files get \(.prefix | bashq) --company \($co | bashq)"' "$MANIFEST")"
|
|
97
97
|
|
|
98
98
|
REPO_IS_NULL="$(jq -r 'if .repo == null then "yes" else "no" end' "$MANIFEST")"
|
|
99
99
|
REPO_SECTION=""
|
|
@@ -135,7 +135,7 @@ while [ "$i" -lt "$PREFIX_COUNT" ]; do
|
|
|
135
135
|
if [ "$BROWSE_RC" -ne 0 ]; then
|
|
136
136
|
echo " FAIL $PFX — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)"
|
|
137
137
|
FAILED=1
|
|
138
|
-
elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ]; then
|
|
138
|
+
elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ] || [ "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" = "Noobjectsunderthatprefix." ]; then
|
|
139
139
|
echo " FAIL $PFX — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/$COMPANY/... and re-run)"
|
|
140
140
|
FAILED=1
|
|
141
141
|
else
|
|
@@ -156,6 +156,31 @@ done
|
|
|
156
156
|
# Browse each distinct parent at most once, and retry an empty result a few
|
|
157
157
|
# times before trusting it. (Cache dir cleaned on exit.)
|
|
158
158
|
REF_BROWSE_CACHE="$(mktemp -d)"
|
|
159
|
+
listing_has_exact_key() {
|
|
160
|
+
local listing="$1" expected="$2"
|
|
161
|
+
printf '%s\n' "$listing" | awk -v expected="$expected" '
|
|
162
|
+
function exact_key(row, expected, modified, size) {
|
|
163
|
+
gsub(/\033\[[0-9;]*m/, "", row)
|
|
164
|
+
sub(/[[:space:]]+$/, "", row)
|
|
165
|
+
if (row !~ / +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/) return 0
|
|
166
|
+
sub(/ +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/, "", row)
|
|
167
|
+
modified = row
|
|
168
|
+
sub(/^.* +/, "", modified)
|
|
169
|
+
if (modified != "—" && modified !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9][0-9][0-9]Z$/) return 0
|
|
170
|
+
sub(/ +[^[:space:]]+$/, "", row)
|
|
171
|
+
sub(/[[:space:]]+$/, "", row)
|
|
172
|
+
size = row
|
|
173
|
+
sub(/^.* +/, "", size)
|
|
174
|
+
if (size !~ /^[0-9]+$/) return 0
|
|
175
|
+
sub(/ +[0-9]+$/, "", row)
|
|
176
|
+
sub(/[[:space:]]+$/, "", row)
|
|
177
|
+
return row == expected
|
|
178
|
+
}
|
|
179
|
+
NR > 2 && exact_key($0, expected) { found = 1 }
|
|
180
|
+
END { exit found ? 0 : 1 }
|
|
181
|
+
'
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
browse_parent() { # parent -> listing (memoized per run, retried on empty)
|
|
160
185
|
local parent="$1" key out attempt
|
|
161
186
|
key="$REF_BROWSE_CACHE/$(printf '%s' "$parent" | tr '/@:' '___')"
|
|
@@ -187,7 +212,7 @@ if [ "$REF_COUNT" -gt 0 ]; then
|
|
|
187
212
|
# early match, which under `set -o pipefail` surfaces printf's SIGPIPE as a
|
|
188
213
|
# non-zero pipeline and FALSE-FAILS a file that is genuinely present (it bit
|
|
189
214
|
# exactly the referenced files that sort early in a large parent listing).
|
|
190
|
-
if
|
|
215
|
+
if listing_has_exact_key "$LISTING" "$kpath"; then
|
|
191
216
|
echo " pass $REL"
|
|
192
217
|
else
|
|
193
218
|
echo " FAIL $REL — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push $kpath --company $COMPANY and re-run)"
|
|
@@ -8,6 +8,7 @@ import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
|
8
8
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
9
|
import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
10
10
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
11
|
+
import { ACL_SOURCE_DISPLAY_LABELS } from "../utils/files-browse-acl.js";
|
|
11
12
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
12
13
|
const FLAG = "core-native-delegate";
|
|
13
14
|
const ASSETS = {
|
|
@@ -113,6 +114,42 @@ function quoteBash(value) {
|
|
|
113
114
|
return value;
|
|
114
115
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
115
116
|
}
|
|
117
|
+
function delegationGroupId(principal) {
|
|
118
|
+
return "grp_dlg" + createHash("sha256").update(principal).digest("hex").slice(0, 32);
|
|
119
|
+
}
|
|
120
|
+
function stripAnsiSequences(value) {
|
|
121
|
+
const escape = String.fromCharCode(27);
|
|
122
|
+
return value.replace(new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
|
|
123
|
+
}
|
|
124
|
+
function groupMemberIds(output) {
|
|
125
|
+
const plainOutput = stripAnsiSequences(output);
|
|
126
|
+
const lines = plainOutput.split(/\r?\n/);
|
|
127
|
+
const header = lines.findIndex((line) => /^PERSON_UID(?:\s|$)/.test(line.trimStart()));
|
|
128
|
+
if (header < 0)
|
|
129
|
+
return null;
|
|
130
|
+
return lines.slice(header + 1).filter((line) => line.trim() !== "")
|
|
131
|
+
.map((line) => line.trim().split(/\s{2,}/, 1)[0] ?? "");
|
|
132
|
+
}
|
|
133
|
+
function browseContainsExactKey(listing, expected) {
|
|
134
|
+
const lines = listing.split(/\r?\n/);
|
|
135
|
+
const aclLabels = Object.values(ACL_SOURCE_DISPLAY_LABELS);
|
|
136
|
+
return lines.slice(2).some((line) => {
|
|
137
|
+
const row = stripAnsiSequences(line).trimEnd();
|
|
138
|
+
const acl = aclLabels.find((label) => row.endsWith(label));
|
|
139
|
+
if (!acl)
|
|
140
|
+
return false;
|
|
141
|
+
const beforeAcl = row.slice(0, -acl.length).trimEnd();
|
|
142
|
+
const modifiedMatch = /^(.*) +(\S+)$/.exec(beforeAcl);
|
|
143
|
+
if (!modifiedMatch)
|
|
144
|
+
return false;
|
|
145
|
+
const modified = modifiedMatch[2] ?? "";
|
|
146
|
+
if (modified !== "—" && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(modified))
|
|
147
|
+
return false;
|
|
148
|
+
const beforeModified = (modifiedMatch[1] ?? "").trimEnd();
|
|
149
|
+
const sizeMatch = /^(.*) +(\d+)$/.exec(beforeModified);
|
|
150
|
+
return (sizeMatch?.[1] ?? "").trimEnd() === expected;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
116
153
|
function parseFlags(args, allowed) {
|
|
117
154
|
const values = {};
|
|
118
155
|
for (let i = 0; i < args.length;) {
|
|
@@ -379,8 +416,12 @@ function runGrant(args, context) {
|
|
|
379
416
|
if (!principal)
|
|
380
417
|
return fail("grant", "manifest has no to.principal");
|
|
381
418
|
let grantPrincipal = principal;
|
|
382
|
-
if (principal.startsWith("agt_"))
|
|
383
|
-
|
|
419
|
+
if (principal.startsWith("agt_")) {
|
|
420
|
+
const legacyGroup = "grp_dlg-" + principal.slice(-9).toLowerCase();
|
|
421
|
+
const nextGroup = delegationGroupId(principal);
|
|
422
|
+
const recordedGroup = text(getPath(manifest, "grantPrincipal"));
|
|
423
|
+
grantPrincipal = recordedGroup === legacyGroup || recordedGroup === nextGroup ? recordedGroup : nextGroup;
|
|
424
|
+
}
|
|
384
425
|
if (status !== "building" && status !== "granted")
|
|
385
426
|
return fail("grant", `manifest status is '${status}' — grants run from 'building' (or re-run from 'granted'), not from there`);
|
|
386
427
|
const prefixes = list(getPath(manifest, "vaultPrefixes"));
|
|
@@ -431,6 +472,13 @@ function runGrant(args, context) {
|
|
|
431
472
|
result = runHq(["groups", "add", grantPrincipal, principal, "--company", company], context, "inherit");
|
|
432
473
|
if (result.code !== 0)
|
|
433
474
|
err("hq-delegate-grant: group add reported an error (may already be a member) — continuing\n");
|
|
475
|
+
const members = subprocess("hq", ["groups", "members", grantPrincipal, "--company", company], context.cwd, { env: { NO_COLOR: "1", FORCE_COLOR: "0" } });
|
|
476
|
+
if (members.code !== 0)
|
|
477
|
+
return fail("grant", "could not verify delegation group membership");
|
|
478
|
+
const memberIds = groupMemberIds(members.stdout ?? "");
|
|
479
|
+
if (memberIds === null || memberIds.length !== 1 || memberIds[0] !== principal) {
|
|
480
|
+
return fail("grant", "delegation group membership does not exactly match recipient");
|
|
481
|
+
}
|
|
434
482
|
}
|
|
435
483
|
let expectedType = "";
|
|
436
484
|
let expectedId = "";
|
|
@@ -794,7 +842,7 @@ function runSend(args, context) {
|
|
|
794
842
|
.map((entry, index) => `${index + 1}. **${text(getPath(entry, "id"))}: ${text(getPath(entry, "title"))}** — ${text(getPath(entry, "description"))}`)
|
|
795
843
|
.join("\n");
|
|
796
844
|
}
|
|
797
|
-
const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) =>
|
|
845
|
+
const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) => "hq files get " + quoteBash(jqRawPath(entry, "prefix")) + " --company " + quoteBash(company)).join("\n");
|
|
798
846
|
let repoSection = "";
|
|
799
847
|
const repoValue = getPath(manifest, "repo");
|
|
800
848
|
if (repoValue !== null && repoValue !== undefined) {
|
|
@@ -824,7 +872,7 @@ function runSend(args, context) {
|
|
|
824
872
|
body += `## The goal\n\n${goal}\n\n`;
|
|
825
873
|
if (stateLine)
|
|
826
874
|
body += `## Where things stand\n\n${stateLine}\n\n`;
|
|
827
|
-
body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
|
|
875
|
+
body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path locally:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
|
|
828
876
|
if (repoSection)
|
|
829
877
|
body += `${repoSection}\n\n`;
|
|
830
878
|
if (secretsSection)
|
|
@@ -1024,7 +1072,7 @@ function runVerify(args, context) {
|
|
|
1024
1072
|
out(` FAIL ${prefix} — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)\n`);
|
|
1025
1073
|
failed = true;
|
|
1026
1074
|
}
|
|
1027
|
-
else if (!combined.replace(/\s/g, "")) {
|
|
1075
|
+
else if (!combined.replace(/\s/g, "") || combined.replace(/\s/g, "") === "Noobjectsunderthatprefix.") {
|
|
1028
1076
|
out(` FAIL ${prefix} — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/${company}/... and re-run)\n`);
|
|
1029
1077
|
failed = true;
|
|
1030
1078
|
}
|
|
@@ -1059,9 +1107,8 @@ function runVerify(args, context) {
|
|
|
1059
1107
|
const relative = reference.slice(`companies/${company}/`.length);
|
|
1060
1108
|
const separator = relative.lastIndexOf("/");
|
|
1061
1109
|
const parent = `${separator < 0 ? "." : relative.slice(0, separator)}/`;
|
|
1062
|
-
const base = separator < 0 ? relative : relative.slice(separator + 1);
|
|
1063
1110
|
const listing = browseParent(parent);
|
|
1064
|
-
if (listing
|
|
1111
|
+
if (browseContainsExactKey(listing, reference))
|
|
1065
1112
|
out(` pass ${relative}\n`);
|
|
1066
1113
|
else {
|
|
1067
1114
|
out(` FAIL ${relative} — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push ${reference} --company ${company} and re-run)\n`);
|
|
@@ -23,6 +23,7 @@ export declare function subprocess(command: string, args: string[], cwd: string,
|
|
|
23
23
|
stdinFd?: number;
|
|
24
24
|
stderr?: "inherit" | "ignore" | "pipe";
|
|
25
25
|
stdout?: "capture" | "inherit" | "ignore";
|
|
26
|
+
env?: NodeJS.ProcessEnv;
|
|
26
27
|
maxBuffer?: number;
|
|
27
28
|
}): CommandResult;
|
|
28
29
|
type JsonRecord = Record<string, unknown>;
|
|
@@ -89,7 +89,7 @@ export function commandExists(name, cwd = process.cwd(), options = {}) {
|
|
|
89
89
|
export function subprocess(command, args, cwd, options = {}) {
|
|
90
90
|
const result = spawnSync(command, args, {
|
|
91
91
|
cwd,
|
|
92
|
-
env: process.env,
|
|
92
|
+
env: { ...process.env, ...options.env },
|
|
93
93
|
input: options.input,
|
|
94
94
|
maxBuffer: options.maxBuffer,
|
|
95
95
|
encoding: "buffer",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
import { Command } from "commander";
|
|
37
37
|
import { ListObjectsV2Command, GetObjectCommand, type ListObjectsV2CommandOutput, type GetObjectCommandOutput } from "@aws-sdk/client-s3";
|
|
38
38
|
import { VaultClient, type ExplicitGrant } from "@indigoai-us/hq-cloud";
|
|
39
|
+
import { type AclSource } from "../utils/files-browse-acl.js";
|
|
40
|
+
export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
|
|
41
|
+
export type { AclSource } from "../utils/files-browse-acl.js";
|
|
39
42
|
/** STS-vended credential set the browse/cat path consumes. */
|
|
40
43
|
export interface BrowseCredentials {
|
|
41
44
|
accessKeyId: string;
|
|
@@ -121,7 +124,6 @@ export type CompanyBrowseClientFactory = (input: {
|
|
|
121
124
|
* - `private-folder`: this is the private folder row itself. Its children
|
|
122
125
|
* are creator-only, so it never covers a child key.
|
|
123
126
|
*/
|
|
124
|
-
export type AclSource = "shared-with-you" | "creator" | "role-bypass" | "personal-vault" | "private-folder";
|
|
125
127
|
export interface BrowseRow {
|
|
126
128
|
key: string;
|
|
127
129
|
size: number;
|
|
@@ -505,5 +507,4 @@ export declare function resolveBrowseSession(): Promise<{
|
|
|
505
507
|
export declare function registerFilesBrowseCommands(filesCmd: Command, deps?: {
|
|
506
508
|
personalS3Factory?: S3ClientFactory;
|
|
507
509
|
}): void;
|
|
508
|
-
export {};
|
|
509
510
|
//# sourceMappingURL=files-browse.d.ts.map
|
|
@@ -42,6 +42,8 @@ import { pipeline } from "node:stream/promises";
|
|
|
42
42
|
import { S3Client, ListObjectsV2Command, GetObjectCommand, } from "@aws-sdk/client-s3";
|
|
43
43
|
import { VaultClient, } from "@indigoai-us/hq-cloud";
|
|
44
44
|
import { isPrivateFolderPattern } from "./_patterns.js";
|
|
45
|
+
import { displayAclSource, } from "../utils/files-browse-acl.js";
|
|
46
|
+
export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
|
|
45
47
|
import { DEFAULT_HQ_ROOT, DEFAULT_COGNITO, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
46
48
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
47
49
|
import { maxLength } from "../utils/table-width.js";
|
|
@@ -142,13 +144,6 @@ export function toBrowseGrantPattern(grantPath, slug) {
|
|
|
142
144
|
const companyRelative = toBucketRelative(grantPath, slug);
|
|
143
145
|
return companyRelative === "*" ? "" : companyRelative;
|
|
144
146
|
}
|
|
145
|
-
function displayAclSource(source) {
|
|
146
|
-
return source === "private-folder"
|
|
147
|
-
? "private folder — children are creator-only"
|
|
148
|
-
: source === "creator"
|
|
149
|
-
? "creator access — creator-only"
|
|
150
|
-
: source;
|
|
151
|
-
}
|
|
152
147
|
/**
|
|
153
148
|
* Bright-line guard for `--out`: refuse to write any byte beneath
|
|
154
149
|
* `<hqRoot>/companies/`. We do NOT enumerate `companies/manifest.yaml`
|
package/dist/commands/lanes.js
CHANGED
|
@@ -2962,6 +2962,7 @@ async function runCreate(ctx, opts) {
|
|
|
2962
2962
|
const config = loadLanesConfig(hqRoot);
|
|
2963
2963
|
let outpostId = opts.outpost?.trim() || undefined;
|
|
2964
2964
|
let placementHostOverride;
|
|
2965
|
+
let placementBalanceHostOverride;
|
|
2965
2966
|
let autoPlacementChoice;
|
|
2966
2967
|
let autoPlacementSkippedHosts = [];
|
|
2967
2968
|
let placementMachineIdentity;
|
|
@@ -3013,12 +3014,14 @@ async function runCreate(ctx, opts) {
|
|
|
3013
3014
|
if (placementToApply !== undefined) {
|
|
3014
3015
|
const currentHost = laneHostFromCtx(ctx);
|
|
3015
3016
|
if (placementToApply.kind === "local") {
|
|
3016
|
-
|
|
3017
|
+
placementBalanceHostOverride = currentHost;
|
|
3018
|
+
placementHostOverride = { kind: "local", id: ctx.hostname() };
|
|
3017
3019
|
outpostId = undefined;
|
|
3018
3020
|
}
|
|
3019
3021
|
else if (placementToApply.kind === "outpost") {
|
|
3020
3022
|
if (currentHost.kind === "outpost" && currentHost.id === placementToApply.id) {
|
|
3021
|
-
|
|
3023
|
+
placementBalanceHostOverride = currentHost;
|
|
3024
|
+
placementHostOverride = { kind: "local", id: ctx.hostname() };
|
|
3022
3025
|
outpostId = undefined;
|
|
3023
3026
|
}
|
|
3024
3027
|
else {
|
|
@@ -3275,7 +3278,7 @@ async function runCreate(ctx, opts) {
|
|
|
3275
3278
|
const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
|
|
3276
3279
|
let accountSelection;
|
|
3277
3280
|
if (provider === "claude") {
|
|
3278
|
-
const targetHost = placementHostOverride ?? (outpostId !== undefined
|
|
3281
|
+
const targetHost = placementBalanceHostOverride ?? placementHostOverride ?? (outpostId !== undefined
|
|
3279
3282
|
? { kind: "outpost", id: outpostId }
|
|
3280
3283
|
: laneHostFromCtx(ctx));
|
|
3281
3284
|
balanceTargetHost = targetHost;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** ACL source values and their stable labels in the hq files browse table. */
|
|
2
|
+
export type AclSource = "shared-with-you" | "creator" | "role-bypass" | "personal-vault" | "private-folder";
|
|
3
|
+
export declare const ACL_SOURCE_DISPLAY_LABELS: Readonly<Record<AclSource, string>>;
|
|
4
|
+
export declare function displayAclSource(source: AclSource): string;
|
|
5
|
+
//# sourceMappingURL=files-browse-acl.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const ACL_SOURCE_DISPLAY_LABELS = {
|
|
2
|
+
"shared-with-you": "shared-with-you",
|
|
3
|
+
creator: "creator access — creator-only",
|
|
4
|
+
"role-bypass": "role-bypass",
|
|
5
|
+
"personal-vault": "personal-vault",
|
|
6
|
+
"private-folder": "private folder — children are creator-only",
|
|
7
|
+
};
|
|
8
|
+
export function displayAclSource(source) {
|
|
9
|
+
return ACL_SOURCE_DISPLAY_LABELS[source];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=files-browse-acl.js.map
|