@indigoai-us/hq-cloud 6.11.18 → 6.11.20
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/.github/workflows/ci.yml +1 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/rescue-core.js +1 -1
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/share.js +25 -19
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +63 -9
- package/dist/cli/share.test.js.map +1 -1
- package/dist/company-resolver.test.js.map +1 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +5 -2
- package/dist/ignore.js.map +1 -1
- package/dist/ignore.test.js +8 -0
- package/dist/ignore.test.js.map +1 -1
- package/dist/lib/conflict-file.js +1 -1
- package/dist/lib/conflict-file.js.map +1 -1
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +0 -3
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/s3.test.js +0 -1
- package/dist/s3.test.js.map +1 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/vault-client.d.ts +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +1 -1
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +15 -0
- package/dist/vault-client.test.js.map +1 -1
- package/eslint.config.js +67 -0
- package/package.json +5 -1
- package/src/bin/sync-runner.ts +0 -1
- package/src/cli/rescue-core.ts +1 -1
- package/src/cli/share.test.ts +79 -11
- package/src/cli/share.ts +24 -20
- package/src/company-resolver.test.ts +1 -1
- package/src/ignore.test.ts +13 -0
- package/src/ignore.ts +5 -2
- package/src/lib/conflict-file.ts +1 -1
- package/src/prefix-coalesce.ts +0 -4
- package/src/s3.test.ts +0 -2
- package/src/sync/push-receiver.test.ts +1 -1
- package/src/vault-client.test.ts +20 -0
- package/src/vault-client.ts +2 -2
package/src/cli/share.ts
CHANGED
|
@@ -1156,14 +1156,7 @@ async function executeUploads(
|
|
|
1156
1156
|
remoteMeta = await headRemoteFile(run.ctx, relativePath);
|
|
1157
1157
|
} catch (headErr) {
|
|
1158
1158
|
if (isAccessDenied(headErr)) {
|
|
1159
|
-
run.
|
|
1160
|
-
type: "error",
|
|
1161
|
-
path: relativePath,
|
|
1162
|
-
message:
|
|
1163
|
-
"skipped: outside granted ACL scope (server returned 403 " +
|
|
1164
|
-
"SCOPE_EXCEEDS_PARENT / access denied on HEAD). Grant this path " +
|
|
1165
|
-
"to push it, or it stays local-only.",
|
|
1166
|
-
});
|
|
1159
|
+
run.scopeExcludedSet.add(relativePath);
|
|
1167
1160
|
return;
|
|
1168
1161
|
}
|
|
1169
1162
|
throw headErr;
|
|
@@ -1325,16 +1318,14 @@ async function executeUploads(
|
|
|
1325
1318
|
counters.filesSkipped++;
|
|
1326
1319
|
return;
|
|
1327
1320
|
}
|
|
1321
|
+
if (isAccessDenied(err)) {
|
|
1322
|
+
run.scopeExcludedSet.add(relativePath);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1328
1325
|
run.emit({
|
|
1329
1326
|
type: "error",
|
|
1330
1327
|
path: relativePath,
|
|
1331
|
-
message:
|
|
1332
|
-
? "skipped: outside granted ACL scope (server returned 403 " +
|
|
1333
|
-
"SCOPE_EXCEEDS_PARENT / access denied on PUT). Grant this path " +
|
|
1334
|
-
"to push it, or it stays local-only."
|
|
1335
|
-
: err instanceof Error
|
|
1336
|
-
? err.message
|
|
1337
|
-
: String(err),
|
|
1328
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1338
1329
|
});
|
|
1339
1330
|
}
|
|
1340
1331
|
};
|
|
@@ -2256,12 +2247,25 @@ async function computeDeletePlan(
|
|
|
2256
2247
|
for (let i = 0; i < headCandidates.length; i += DELETE_PLAN_HEAD_CONCURRENCY) {
|
|
2257
2248
|
const chunk = headCandidates.slice(i, i + DELETE_PLAN_HEAD_CONCURRENCY);
|
|
2258
2249
|
const results = await Promise.all(
|
|
2259
|
-
chunk.map(async (c) =>
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2250
|
+
chunk.map(async (c) => {
|
|
2251
|
+
try {
|
|
2252
|
+
return {
|
|
2253
|
+
candidate: c,
|
|
2254
|
+
remote: await headRemoteFile(ctx, c.key),
|
|
2255
|
+
scopeExcluded: false,
|
|
2256
|
+
};
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
if (isAccessDenied(err)) {
|
|
2259
|
+
return { candidate: c, remote: null, scopeExcluded: true };
|
|
2260
|
+
}
|
|
2261
|
+
throw err;
|
|
2262
|
+
}
|
|
2263
|
+
}),
|
|
2263
2264
|
);
|
|
2264
|
-
for (const { candidate, remote } of results) {
|
|
2265
|
+
for (const { candidate, remote, scopeExcluded } of results) {
|
|
2266
|
+
if (scopeExcluded) {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2265
2269
|
if (remote === null) {
|
|
2266
2270
|
plan.toTombstone.push(candidate.key);
|
|
2267
2271
|
continue;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* the trailing-slash sibling boundary, and the unattributed (no-match) path.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { describe, it, expect,
|
|
7
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as os from "os";
|
|
10
10
|
import * as path from "path";
|
package/src/ignore.test.ts
CHANGED
|
@@ -49,6 +49,19 @@ describe("createIgnoreFilter", () => {
|
|
|
49
49
|
expect(shouldSync(path.join(hqRoot, "node_modules/react/index.js"))).toBe(false);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
it("repos/ exclusion is root-anchored to hqRoot (DEV-1791)", () => {
|
|
53
|
+
const shouldSync = createIgnoreFilter(hqRoot);
|
|
54
|
+
expect(shouldSync(path.join(hqRoot, "repos/private/hq-cloud/src/x.ts"))).toBe(false);
|
|
55
|
+
expect(shouldSync(path.join(hqRoot, "repos/public/hq-core/README.md"))).toBe(false);
|
|
56
|
+
expect(
|
|
57
|
+
shouldSync(path.join(hqRoot, "companies/lutiq/knowledge/repos/dev-docs-mcp/overview.md")),
|
|
58
|
+
).toBe(true);
|
|
59
|
+
expect(shouldSync(path.join(hqRoot, "companies/indigo/knowledge/repos/foo/notes.md"))).toBe(
|
|
60
|
+
true,
|
|
61
|
+
);
|
|
62
|
+
expect(shouldSync(path.join(hqRoot, "companies/lutiq/knowledge/repos"), true)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
52
65
|
it("permissive mode: .hqignore patterns are honored", () => {
|
|
53
66
|
fs.writeFileSync(path.join(hqRoot, ".hqignore"), "companies/*/data/\n");
|
|
54
67
|
const shouldSync = createIgnoreFilter(hqRoot);
|
package/src/ignore.ts
CHANGED
|
@@ -119,8 +119,11 @@ export const DEFAULT_IGNORES = [
|
|
|
119
119
|
"**/.claude/state/",
|
|
120
120
|
"**/.claude/audit/",
|
|
121
121
|
|
|
122
|
-
// HQ repos directory (managed separately, not synced)
|
|
123
|
-
|
|
122
|
+
// HQ repos directory (managed separately, not synced). Root-anchored so
|
|
123
|
+
// only hqRoot/repos/ is excluded; bare `repos/` would match any nested
|
|
124
|
+
// directory named repos (for example companies/<co>/knowledge/repos/),
|
|
125
|
+
// silently dropping discover-generated knowledge (DEV-1791).
|
|
126
|
+
"/repos/",
|
|
124
127
|
|
|
125
128
|
// Secrets / env
|
|
126
129
|
".env",
|
package/src/lib/conflict-file.ts
CHANGED
|
@@ -76,6 +76,6 @@ export function buildConflictId(
|
|
|
76
76
|
detectedAt: string,
|
|
77
77
|
): string {
|
|
78
78
|
const safeTs = detectedAt.replace(/:/g, "-").replace(/\.\d+/, "");
|
|
79
|
-
const safePath = originalRelative.replace(/[
|
|
79
|
+
const safePath = originalRelative.replace(/[/\\.]/g, "-");
|
|
80
80
|
return `${safePath}-${safeTs}`;
|
|
81
81
|
}
|
package/src/prefix-coalesce.ts
CHANGED
|
@@ -42,10 +42,6 @@ const SCOPE_PREFIX_MARKER = "\u0000hq-scope:";
|
|
|
42
42
|
const SCOPE_PREFIX_MARKER_PREFIX = `${SCOPE_PREFIX_MARKER}prefix`;
|
|
43
43
|
const SCOPE_PREFIX_MARKER_EXACT = `${SCOPE_PREFIX_MARKER}exact`;
|
|
44
44
|
|
|
45
|
-
function isScopePrefixEntry(value: ScopePrefixInput): value is ScopePrefixEntry {
|
|
46
|
-
return typeof value !== "string";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
45
|
function inferStringMatch(prefix: string): ScopePrefixMatch {
|
|
50
46
|
return prefix === "" || prefix.endsWith("/") ? "prefix" : "exact";
|
|
51
47
|
}
|
package/src/s3.test.ts
CHANGED
|
@@ -294,8 +294,6 @@ describe("uploadFile", () => {
|
|
|
294
294
|
it("preserves the existing created-at on re-upload (NEW-pill ageing window)", async () => {
|
|
295
295
|
// First upload happened a week ago; second run must keep that timestamp
|
|
296
296
|
// so the hq-console "NEW" pill doesn't reset on every sync tick.
|
|
297
|
-
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
298
|
-
|
|
299
297
|
// Override the FakeS3Client's HeadObject to return the legacy timestamp
|
|
300
298
|
// for this one test. The mock factory returns a fresh object per send
|
|
301
299
|
// invocation so we patch at the class level via a one-shot wrapper.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* fake SQS client / in-memory fanout.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { describe, expect, it } from "vitest";
|
|
26
26
|
|
|
27
27
|
import { encodePushEvent, type PushEvent } from "./push-event.js";
|
|
28
28
|
import { StaticFlagProvider } from "./feature-flags.js";
|
package/src/vault-client.test.ts
CHANGED
|
@@ -830,6 +830,26 @@ describe("listMyExplicitGrants (US-004)", () => {
|
|
|
830
830
|
expect(init.body).toBeUndefined();
|
|
831
831
|
});
|
|
832
832
|
|
|
833
|
+
it("parses creator-sourced grants", async () => {
|
|
834
|
+
const grant: ExplicitGrant = {
|
|
835
|
+
companyUid: "cmp_abc",
|
|
836
|
+
path: "personal/psn_creator",
|
|
837
|
+
permission: "admin",
|
|
838
|
+
source: "creator",
|
|
839
|
+
};
|
|
840
|
+
fetchSpy.mockResolvedValueOnce(
|
|
841
|
+
jsonResponse(200, {
|
|
842
|
+
grants: [grant],
|
|
843
|
+
computedAt: "2026-05-20T12:00:00.000Z",
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const grants = await client.listMyExplicitGrants("cmp_abc");
|
|
848
|
+
|
|
849
|
+
expect(grants[0]?.source).toBe("creator");
|
|
850
|
+
expect(grants).toEqual([grant]);
|
|
851
|
+
});
|
|
852
|
+
|
|
833
853
|
it("URL-encodes the companyUid query parameter", async () => {
|
|
834
854
|
fetchSpy.mockResolvedValueOnce(
|
|
835
855
|
jsonResponse(200, { grants: [], computedAt: "2026-05-20T12:00:00.000Z" }),
|
package/src/vault-client.ts
CHANGED
|
@@ -187,7 +187,7 @@ export interface CreateEntityResult {
|
|
|
187
187
|
* `granteeType: 'company-wide'` row. Both mean "every active member of
|
|
188
188
|
* this company sees this prefix".
|
|
189
189
|
*/
|
|
190
|
-
export type GrantSource = "person" | "email" | "group" | "open";
|
|
190
|
+
export type GrantSource = "creator" | "person" | "email" | "group" | "open";
|
|
191
191
|
|
|
192
192
|
/** Permission level surfaced on a grant row. Matches `AclPermission`. */
|
|
193
193
|
export type GrantPermission = "read" | "write" | "admin";
|
|
@@ -478,7 +478,7 @@ type VaultResponseSchema<T> = z.ZodType<T>;
|
|
|
478
478
|
const membershipRoleSchema = z.enum(["owner", "admin", "member", "guest"]);
|
|
479
479
|
const membershipStatusSchema = z.enum(["pending", "active", "revoked"]);
|
|
480
480
|
const grantPermissionSchema = z.enum(["read", "write", "admin"]);
|
|
481
|
-
const grantSourceSchema = z.enum(["person", "email", "group", "open"]);
|
|
481
|
+
const grantSourceSchema = z.enum(["creator", "person", "email", "group", "open"]);
|
|
482
482
|
const presignOpSchema = z.enum(["get", "put", "delete"]);
|
|
483
483
|
const syncModeSchema = z.enum(["shared", "all", "custom"]);
|
|
484
484
|
const vendPurposeSchema = z.enum(["sync", "browse"]);
|