@indigoai-us/hq-cloud 6.12.1 → 6.12.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.
Files changed (58) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +10 -0
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-events.d.ts +18 -0
  5. package/dist/bin/sync-runner-events.d.ts.map +1 -1
  6. package/dist/bin/sync-runner-events.js +26 -0
  7. package/dist/bin/sync-runner-events.js.map +1 -1
  8. package/dist/bin/sync-runner-events.test.d.ts +2 -0
  9. package/dist/bin/sync-runner-events.test.d.ts.map +1 -0
  10. package/dist/bin/sync-runner-events.test.js +90 -0
  11. package/dist/bin/sync-runner-events.test.js.map +1 -0
  12. package/dist/bin/sync-runner.d.ts +6 -0
  13. package/dist/bin/sync-runner.d.ts.map +1 -1
  14. package/dist/bin/sync-runner.js +10 -2
  15. package/dist/bin/sync-runner.js.map +1 -1
  16. package/dist/bin/sync-runner.test.js +1 -0
  17. package/dist/bin/sync-runner.test.js.map +1 -1
  18. package/dist/cli/reindex.d.ts.map +1 -1
  19. package/dist/cli/reindex.js +25 -2
  20. package/dist/cli/reindex.js.map +1 -1
  21. package/dist/cli/reindex.test.js +51 -1
  22. package/dist/cli/reindex.test.js.map +1 -1
  23. package/dist/cli/share.d.ts +16 -0
  24. package/dist/cli/share.d.ts.map +1 -1
  25. package/dist/cli/share.js +62 -3
  26. package/dist/cli/share.js.map +1 -1
  27. package/dist/cli/share.test.js +101 -0
  28. package/dist/cli/share.test.js.map +1 -1
  29. package/dist/cli/sync.d.ts +15 -0
  30. package/dist/cli/sync.d.ts.map +1 -1
  31. package/dist/cli/sync.js +9 -0
  32. package/dist/cli/sync.js.map +1 -1
  33. package/dist/ignore.d.ts +14 -0
  34. package/dist/ignore.d.ts.map +1 -1
  35. package/dist/ignore.js +66 -0
  36. package/dist/ignore.js.map +1 -1
  37. package/dist/ignore.test.js +43 -1
  38. package/dist/ignore.test.js.map +1 -1
  39. package/dist/object-io.d.ts.map +1 -1
  40. package/dist/object-io.js +15 -0
  41. package/dist/object-io.js.map +1 -1
  42. package/dist/object-io.test.js +52 -0
  43. package/dist/object-io.test.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/bin/sync-runner-company.ts +9 -0
  46. package/src/bin/sync-runner-events.test.ts +112 -0
  47. package/src/bin/sync-runner-events.ts +30 -0
  48. package/src/bin/sync-runner.test.ts +1 -0
  49. package/src/bin/sync-runner.ts +14 -4
  50. package/src/cli/reindex.test.ts +68 -1
  51. package/src/cli/reindex.ts +27 -2
  52. package/src/cli/share.test.ts +120 -0
  53. package/src/cli/share.ts +81 -3
  54. package/src/cli/sync.ts +26 -0
  55. package/src/ignore.test.ts +54 -1
  56. package/src/ignore.ts +73 -0
  57. package/src/object-io.test.ts +54 -0
  58. package/src/object-io.ts +18 -0
@@ -19,6 +19,7 @@ import {
19
19
  } from "./object-io.js";
20
20
  import type { EntityContext } from "./types.js";
21
21
  import type { PresignResultRow, VaultListedObject } from "./vault-client.js";
22
+ import { isAccessDenied } from "./sync-core.js";
22
23
 
23
24
  const COMPANY = "cmp_test";
24
25
 
@@ -205,6 +206,59 @@ describe("PresignObjectIO.putObject", () => {
205
206
  expect(fetchMock).not.toHaveBeenCalled();
206
207
  });
207
208
 
209
+ it("a write denial (FILES_PRESIGN_FORBIDDEN) throws the access-denied shape so the push skips it like a denied GET", async () => {
210
+ const { vault, setPresign } = makeVault();
211
+ // The server refuses to MINT a PUT URL for a key the caller can't write
212
+ // (private-by-default: members hold read-only on a shared knowledge prefix).
213
+ setPresign([
214
+ {
215
+ key: "knowledge/clients/acme/brief.md",
216
+ op: "put",
217
+ error: "Caller lacks write permission on 'knowledge/clients/acme/brief.md'",
218
+ code: "FILES_PRESIGN_FORBIDDEN",
219
+ },
220
+ ]);
221
+ const io = new PresignObjectIO(vault, COMPANY);
222
+ let caught: unknown;
223
+ await io
224
+ .putObject({
225
+ key: "knowledge/clients/acme/brief.md",
226
+ body: Buffer.from("z"),
227
+ contentType: "text/markdown",
228
+ })
229
+ .catch((e) => {
230
+ caught = e;
231
+ });
232
+ // Same shape a denied GET/HEAD already throws (name === "Forbidden"), so the
233
+ // share.ts push catch routes it through `isAccessDenied` into the silent
234
+ // per-path skip (scopeExcludedSet) + the "N paths skipped — outside your
235
+ // granted access" aggregate, instead of a fatal `type:"error"` that exits
236
+ // the runner non-zero and floods Sentry. This is the PUT/GET symmetry that
237
+ // fixes the fleet-wide presign-put-denied crash-loop.
238
+ expect((caught as Error).name).toBe("Forbidden");
239
+ expect(isAccessDenied(caught)).toBe(true);
240
+ // Denied at mint time — the byte-moving PUT is never attempted.
241
+ expect(fetchMock).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it("a non-authorization presign failure stays a generic (fatal) error — not silently skipped", async () => {
245
+ const { vault, setPresign } = makeVault();
246
+ setPresign([
247
+ { key: "secret/x", op: "put", error: "boom", code: "PRESIGN_INTERNAL" },
248
+ ]);
249
+ const io = new PresignObjectIO(vault, COMPANY);
250
+ let caught: unknown;
251
+ await io
252
+ .putObject({ key: "secret/x", body: Buffer.from("z"), contentType: "x" })
253
+ .catch((e) => {
254
+ caught = e;
255
+ });
256
+ // Only `*_FORBIDDEN` authorization denials are reclassified; a real backend
257
+ // failure must still surface loudly so it is not masked as an expected skip.
258
+ expect(isAccessDenied(caught)).toBe(false);
259
+ expect((caught as Error).message).toMatch(/denied for secret\/x.*PRESIGN_INTERNAL/);
260
+ });
261
+
208
262
  it("throws when the PUT itself fails", async () => {
209
263
  const { vault, setPresign } = makeVault();
210
264
  setPresign([{ key: "k", op: "put", url: "https://s3/put" }]);
package/src/object-io.ts CHANGED
@@ -354,6 +354,24 @@ function firstRowOrThrow(
354
354
  throw new Error(`presign ${op} returned no row for ${key}`);
355
355
  }
356
356
  if (row.error || !row.url) {
357
+ // An authorization denial (server `*_FORBIDDEN` code — e.g.
358
+ // FILES_PRESIGN_FORBIDDEN when the caller lacks write on a shared prefix,
359
+ // or FILES_RAW_FORBIDDEN) must throw the SAME `name: "Forbidden"` shape the
360
+ // GET/HEAD path uses (see {@link accessDeniedError}) so it routes through
361
+ // the `isAccessDenied` skip in sync.ts / share.ts. Without this, a denied
362
+ // PUT presign fell to the generic Error below — `isAccessDenied` returned
363
+ // false — and the per-file ACL skip the GET path handles gracefully became
364
+ // a FATAL sync error: the crash-loop + Sentry flood behind the fleet-wide
365
+ // "presign put denied" incident (members pushing net-new keys to a
366
+ // company-wide-read prefix under private-by-default). The denial is
367
+ // expected; only the fatal handling of it was the bug.
368
+ if (typeof row.code === "string" && row.code.endsWith("_FORBIDDEN")) {
369
+ throw accessDeniedError(
370
+ key,
371
+ `no write access to '${key}' (server ${op} denied: ${row.code}) — ` +
372
+ `ask an admin to grant write on this shared prefix`,
373
+ );
374
+ }
357
375
  throw new Error(
358
376
  `presign ${op} denied for ${key}: ${row.error ?? "no url"}${
359
377
  row.code ? ` (${row.code})` : ""