@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.
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +10 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-events.d.ts +18 -0
- package/dist/bin/sync-runner-events.d.ts.map +1 -1
- package/dist/bin/sync-runner-events.js +26 -0
- package/dist/bin/sync-runner-events.js.map +1 -1
- package/dist/bin/sync-runner-events.test.d.ts +2 -0
- package/dist/bin/sync-runner-events.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-events.test.js +90 -0
- package/dist/bin/sync-runner-events.test.js.map +1 -0
- package/dist/bin/sync-runner.d.ts +6 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +10 -2
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +1 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +25 -2
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +51 -1
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/share.d.ts +16 -0
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +62 -3
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +101 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +15 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +9 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/ignore.d.ts +14 -0
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +66 -0
- package/dist/ignore.js.map +1 -1
- package/dist/ignore.test.js +43 -1
- package/dist/ignore.test.js.map +1 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +15 -0
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +52 -0
- package/dist/object-io.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +9 -0
- package/src/bin/sync-runner-events.test.ts +112 -0
- package/src/bin/sync-runner-events.ts +30 -0
- package/src/bin/sync-runner.test.ts +1 -0
- package/src/bin/sync-runner.ts +14 -4
- package/src/cli/reindex.test.ts +68 -1
- package/src/cli/reindex.ts +27 -2
- package/src/cli/share.test.ts +120 -0
- package/src/cli/share.ts +81 -3
- package/src/cli/sync.ts +26 -0
- package/src/ignore.test.ts +54 -1
- package/src/ignore.ts +73 -0
- package/src/object-io.test.ts +54 -0
- package/src/object-io.ts +18 -0
package/src/object-io.test.ts
CHANGED
|
@@ -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})` : ""
|