@indigoai-us/hq-cli 5.345.133 → 5.345.134
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 +10 -3
- package/dist/commands/access.d.ts +2 -0
- package/dist/commands/access.js +58 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.134] — 2026-10-15
|
|
6
|
+
- `hq access --as <member>` now honors the most specific ACL row covering a path, so an empty folder lock reports no access instead of the write access of a broader grant. `hq access` also reports "no access" rather than "never created" when a caller cannot list a path that an ACL lock denies them.
|
|
7
|
+
|
|
5
8
|
## [5.345.133] — 2026-10-15
|
|
9
|
+
- Pipeline loop lanes no longer hang after their first phase. 5.345.132 kept the lane running while its next phase resumed, which left the previous phase's watcher holding the lane, so the next phase's result was never recorded. That change is reverted; a loop lane may again show as done for a few seconds while its next phase starts.
|
|
10
|
+
|
|
11
|
+
## [5.345.132] — 2026-10-14
|
|
12
|
+
- A loop lane starting its next phase now stays running in `hq lanes status` and `hq lanes list` while the phase resumes, instead of showing done for several seconds.
|
|
13
|
+
- Recognize Git Bash on Git for Windows 2.56+, which reports `MSYSTEM=UCRT64` instead of `MINGW64`, so delegate jq calls keep their byte-exact output and journal titles with newlines get a valid file name on Windows.
|
|
14
|
+
|
|
15
|
+
## [5.345.131] — 2026-10-14
|
|
6
16
|
- `hq lanes --account auto` and `hq capacity route` no longer refuse every signed-in account when the capacity collector is off or its readings are stale; those accounts are picked with unknown usage, below accounts with fresh headroom.
|
|
7
17
|
- `hq lanes create` now resumes an earlier worker session only when the project, model, and effort match, and otherwise starts a fresh thread.
|
|
8
|
-
- A loop lane starting its next phase now stays running in `hq lanes status` and `hq lanes list` while the phase resumes, instead of showing done for several seconds.
|
|
9
18
|
- A loop lane waiting for its next phase now shows as running in `hq lanes status` and `hq lanes list`, and becomes done only after it is stopped.
|
|
10
|
-
- Recognize Git Bash on Git for Windows 2.56+, which reports `MSYSTEM=UCRT64` instead of `MINGW64`, so delegate jq calls keep their byte-exact output and journal titles with newlines get a valid file name on Windows.
|
|
11
|
-
- Pipeline loop lanes no longer hang after their first phase. 5.345.132 kept the lane running while its next phase resumed, which left the previous phase's watcher holding the lane, so the next phase's result was never recorded. That change is reverted; a loop lane may again show as done for a few seconds while its next phase starts.
|
|
12
19
|
|
|
13
20
|
## [5.345.130] — 2026-10-14
|
|
14
21
|
- Add native TypeScript `hq core workflow run` orchestration with the bundled runner as its flag-off fallback.
|
|
@@ -48,6 +48,8 @@ export interface AccessAclTree {
|
|
|
48
48
|
updatedAt: string;
|
|
49
49
|
} | null;
|
|
50
50
|
effectivePermission: string | null;
|
|
51
|
+
/** Most-specific ACL row covering this prefix, including empty lock rows. */
|
|
52
|
+
coveringAclPath?: string | null;
|
|
51
53
|
}
|
|
52
54
|
export interface AccessMember {
|
|
53
55
|
personUid: string;
|
package/dist/commands/access.js
CHANGED
|
@@ -91,6 +91,27 @@ function fileGrantCoversPath(grantPath, targetPath) {
|
|
|
91
91
|
return classifyAclSource(targetPath, [grantPath]) !== "role-bypass";
|
|
92
92
|
}
|
|
93
93
|
const FILE_PERMISSION_RANK = { read: 1, write: 2, admin: 3 };
|
|
94
|
+
function aclPatternSpecificity(pattern) {
|
|
95
|
+
if (pattern === "*" || pattern === "")
|
|
96
|
+
return [0, 0];
|
|
97
|
+
const glob = pattern.endsWith("/*");
|
|
98
|
+
const body = glob ? pattern.slice(0, -2) : pattern;
|
|
99
|
+
const segs = body.split("/").filter(Boolean).length;
|
|
100
|
+
return [segs, glob ? 0 : 1];
|
|
101
|
+
}
|
|
102
|
+
function pickMostSpecificCoveringRow(rows, targetPath, company) {
|
|
103
|
+
const covering = rows.filter((row) => fileGrantCoversPath(toBucketRelative(row.path, company), targetPath));
|
|
104
|
+
if (covering.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
covering.sort((a, b) => {
|
|
107
|
+
const [aSegs, aExact] = aclPatternSpecificity(toBucketRelative(a.path, company));
|
|
108
|
+
const [bSegs, bExact] = aclPatternSpecificity(toBucketRelative(b.path, company));
|
|
109
|
+
if (aSegs !== bSegs)
|
|
110
|
+
return bSegs - aSegs;
|
|
111
|
+
return bExact - aExact;
|
|
112
|
+
});
|
|
113
|
+
return covering[0] ?? null;
|
|
114
|
+
}
|
|
94
115
|
function httpStatusOf(err) {
|
|
95
116
|
if (!err || typeof err !== "object")
|
|
96
117
|
return undefined;
|
|
@@ -146,10 +167,13 @@ function treeIsEmpty(tree) {
|
|
|
146
167
|
function aclDenied(tree) {
|
|
147
168
|
if (!tree)
|
|
148
169
|
return false;
|
|
170
|
+
const perm = tree.effectivePermission;
|
|
171
|
+
const denied = perm === null || perm === "none" || perm === "deny";
|
|
172
|
+
if (tree.coveringAclPath && denied)
|
|
173
|
+
return true;
|
|
149
174
|
if (treeIsEmpty(tree))
|
|
150
175
|
return false;
|
|
151
|
-
|
|
152
|
-
return perm === null || perm === "none" || perm === "deny";
|
|
176
|
+
return denied;
|
|
153
177
|
}
|
|
154
178
|
/**
|
|
155
179
|
* Build the result and record one cause-mix telemetry line. Every exit path
|
|
@@ -289,6 +313,18 @@ export async function runAccess(input) {
|
|
|
289
313
|
}
|
|
290
314
|
else if (fuzzy.length === 0) {
|
|
291
315
|
steps.push("list: 0 hits");
|
|
316
|
+
const tree = await deps.fetchAclTree(relative);
|
|
317
|
+
if (aclDenied(tree)) {
|
|
318
|
+
steps.push("acl: deny");
|
|
319
|
+
const result = finish("no-access", {
|
|
320
|
+
path: anchored(relative) || input.target,
|
|
321
|
+
company: slug,
|
|
322
|
+
exists: true,
|
|
323
|
+
steps,
|
|
324
|
+
}, finishCtx);
|
|
325
|
+
emit(input, result, log);
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
292
328
|
const result = finish("never-existed", {
|
|
293
329
|
path: anchored(relative) || input.target,
|
|
294
330
|
company: slug,
|
|
@@ -632,7 +668,7 @@ async function promptPick(candidates) {
|
|
|
632
668
|
function treeFromBody(body) {
|
|
633
669
|
if (!body)
|
|
634
670
|
return null;
|
|
635
|
-
if (treeIsEmpty(body))
|
|
671
|
+
if (treeIsEmpty(body) && !body.coveringAclPath)
|
|
636
672
|
return null;
|
|
637
673
|
return body;
|
|
638
674
|
}
|
|
@@ -730,12 +766,25 @@ export async function runAccessForPerson(rawPath, personUid, opts) {
|
|
|
730
766
|
}
|
|
731
767
|
const roleBypass = projection.files.roleBypass === true;
|
|
732
768
|
const grants = Array.isArray(projection.files.grants) ? projection.files.grants : [];
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
769
|
+
const locks = Array.isArray(projection.files.locks) ? projection.files.locks : [];
|
|
770
|
+
const coveringRows = [];
|
|
771
|
+
for (const grant of grants) {
|
|
772
|
+
if (typeof grant.path === "string" &&
|
|
773
|
+
typeof grant.permission === "string" &&
|
|
774
|
+
grant.permission in FILE_PERMISSION_RANK) {
|
|
775
|
+
coveringRows.push({
|
|
776
|
+
path: grant.path,
|
|
777
|
+
permission: grant.permission,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
for (const lock of locks) {
|
|
782
|
+
if (typeof lock.path === "string") {
|
|
783
|
+
coveringRows.push({ path: lock.path, permission: null });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const winner = pickMostSpecificCoveringRow(coveringRows, relativePath, company);
|
|
787
|
+
const permission = roleBypass ? "admin" : (winner?.permission ?? null);
|
|
739
788
|
return {
|
|
740
789
|
personUid: uid,
|
|
741
790
|
company,
|