@indigoai-us/hq-cloud 6.2.2 → 6.2.4
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.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +8 -0
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +32 -56
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-journal-reconcile.test.js +100 -49
- package/dist/cli/rescue-journal-reconcile.test.js.map +1 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +6 -1
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/sync-scope.test.js +33 -1
- package/dist/cli/sync-scope.test.js.map +1 -1
- package/dist/cli/sync.d.ts +8 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +24 -2
- package/dist/cli/sync.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +7 -1
- package/dist/journal.js.map +1 -1
- package/dist/public-surface.test.js +5 -0
- package/dist/public-surface.test.js.map +1 -1
- package/dist/remote-pull.d.ts +7 -0
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +5 -0
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +110 -0
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/scope-shrink.d.ts +20 -0
- package/dist/scope-shrink.d.ts.map +1 -1
- package/dist/scope-shrink.js +11 -0
- package/dist/scope-shrink.js.map +1 -1
- package/dist/scope-shrink.test.js +122 -0
- package/dist/scope-shrink.test.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bin/sync-runner.ts +8 -0
- package/src/cli/rescue-core.ts +36 -71
- package/src/cli/rescue-journal-reconcile.test.ts +113 -54
- package/src/cli/share.ts +6 -0
- package/src/cli/sync-scope.test.ts +35 -1
- package/src/cli/sync.ts +32 -0
- package/src/index.ts +7 -0
- package/src/journal.ts +7 -0
- package/src/public-surface.test.ts +5 -0
- package/src/remote-pull.test.ts +118 -0
- package/src/remote-pull.ts +12 -0
- package/src/scope-shrink.test.ts +128 -0
- package/src/scope-shrink.ts +29 -0
- package/src/types.ts +12 -0
package/src/remote-pull.test.ts
CHANGED
|
@@ -594,6 +594,9 @@ describe("pullCompany (engine orchestrator)", () => {
|
|
|
594
594
|
size: 8,
|
|
595
595
|
syncedAt: new Date().toISOString(),
|
|
596
596
|
direction: "down",
|
|
597
|
+
// Authored by someone else (no matching callerSub passed below), so
|
|
598
|
+
// the authorship guard correctly leaves it eligible for shrink.
|
|
599
|
+
createdBySub: "uploader-sub",
|
|
597
600
|
},
|
|
598
601
|
},
|
|
599
602
|
pulls: [
|
|
@@ -651,12 +654,14 @@ describe("pullCompany (engine orchestrator)", () => {
|
|
|
651
654
|
size: 8,
|
|
652
655
|
syncedAt: new Date().toISOString(),
|
|
653
656
|
direction: "down",
|
|
657
|
+
createdBySub: "uploader-sub", // foreign author — eligible for shrink
|
|
654
658
|
},
|
|
655
659
|
"companies/indigo/scratch/clean.md": {
|
|
656
660
|
hash: sha256("clean"),
|
|
657
661
|
size: 5,
|
|
658
662
|
syncedAt: new Date().toISOString(),
|
|
659
663
|
direction: "down",
|
|
664
|
+
createdBySub: "uploader-sub", // foreign author — eligible for shrink
|
|
660
665
|
},
|
|
661
666
|
},
|
|
662
667
|
pulls: [
|
|
@@ -711,6 +716,7 @@ describe("pullCompany (engine orchestrator)", () => {
|
|
|
711
716
|
size: 5,
|
|
712
717
|
syncedAt: new Date(Date.now() - 60_000).toISOString(),
|
|
713
718
|
direction: "down",
|
|
719
|
+
createdBySub: "uploader-sub", // foreign author — eligible for shrink
|
|
714
720
|
},
|
|
715
721
|
},
|
|
716
722
|
};
|
|
@@ -741,6 +747,118 @@ describe("pullCompany (engine orchestrator)", () => {
|
|
|
741
747
|
expect(journal.version).toBe("2"); // migrated by appendPullRecord
|
|
742
748
|
});
|
|
743
749
|
|
|
750
|
+
it("retains a caller-authored orphan across a scope shrink (own work is never pruned)", async () => {
|
|
751
|
+
// The owner narrowed to `shared`, but `projects/mine.md` is their own work
|
|
752
|
+
// (createdBySub === callerSub). A scope shrink must NOT prune it — mode
|
|
753
|
+
// only governs mirroring OTHER people's files.
|
|
754
|
+
const mineAbs = path.join(hqRoot, "companies/indigo/projects/mine.md");
|
|
755
|
+
fs.mkdirSync(path.dirname(mineAbs), { recursive: true });
|
|
756
|
+
fs.writeFileSync(mineAbs, "mine");
|
|
757
|
+
const past = Date.now() - 60_000;
|
|
758
|
+
fs.utimesSync(mineAbs, past / 1000, past / 1000);
|
|
759
|
+
|
|
760
|
+
const journal: SyncJournal = {
|
|
761
|
+
version: "2",
|
|
762
|
+
lastSync: "",
|
|
763
|
+
files: {
|
|
764
|
+
"companies/indigo/projects/mine.md": {
|
|
765
|
+
hash: sha256("mine"),
|
|
766
|
+
size: 4,
|
|
767
|
+
syncedAt: new Date().toISOString(),
|
|
768
|
+
direction: "down",
|
|
769
|
+
createdBySub: "owner-sub",
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
pulls: [
|
|
773
|
+
{
|
|
774
|
+
pullId: "01PREV",
|
|
775
|
+
companyUid: "cmp_indigo",
|
|
776
|
+
startedAt: "2026-05-19T00:00:00.000Z",
|
|
777
|
+
completedAt: "2026-05-19T00:00:05.000Z",
|
|
778
|
+
syncMode: "all",
|
|
779
|
+
prefixSet: ["companies/indigo/"],
|
|
780
|
+
scopeChangeDetected: false,
|
|
781
|
+
orphansRemoved: 0,
|
|
782
|
+
orphansBlocked: 0,
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
const result = await pullCompany({
|
|
788
|
+
ctx: makeCtx(),
|
|
789
|
+
journal,
|
|
790
|
+
hqRoot,
|
|
791
|
+
callerSub: "owner-sub",
|
|
792
|
+
scope: {
|
|
793
|
+
companyUid: "cmp_indigo",
|
|
794
|
+
syncMode: "shared",
|
|
795
|
+
prefixSet: ["companies/indigo/meetings/"],
|
|
796
|
+
strategy: "vend-fanout",
|
|
797
|
+
},
|
|
798
|
+
listFn: async () => [],
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
expect(result.pullRecord.scopeChangeDetected).toBe(false);
|
|
802
|
+
expect(result.pullRecord.orphansRemoved).toBe(0);
|
|
803
|
+
expect(fs.existsSync(mineAbs)).toBe(true); // own work preserved
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("retains an unknown-author orphan on the automatic path (conservative, never auto-deletes)", async () => {
|
|
807
|
+
// A legacy entry with no recorded author. The background pull must not make
|
|
808
|
+
// a destructive guess — retain it (the explicit narrow ritual is the
|
|
809
|
+
// confirmed path that can reclaim it).
|
|
810
|
+
const legacyAbs = path.join(hqRoot, "companies/indigo/projects/legacy.md");
|
|
811
|
+
fs.mkdirSync(path.dirname(legacyAbs), { recursive: true });
|
|
812
|
+
fs.writeFileSync(legacyAbs, "legacy");
|
|
813
|
+
const past = Date.now() - 60_000;
|
|
814
|
+
fs.utimesSync(legacyAbs, past / 1000, past / 1000);
|
|
815
|
+
|
|
816
|
+
const journal: SyncJournal = {
|
|
817
|
+
version: "2",
|
|
818
|
+
lastSync: "",
|
|
819
|
+
files: {
|
|
820
|
+
"companies/indigo/projects/legacy.md": {
|
|
821
|
+
hash: sha256("legacy"),
|
|
822
|
+
size: 6,
|
|
823
|
+
syncedAt: new Date().toISOString(),
|
|
824
|
+
direction: "down",
|
|
825
|
+
// no createdBySub — predates author stamping
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
pulls: [
|
|
829
|
+
{
|
|
830
|
+
pullId: "01PREV",
|
|
831
|
+
companyUid: "cmp_indigo",
|
|
832
|
+
startedAt: "2026-05-19T00:00:00.000Z",
|
|
833
|
+
completedAt: "2026-05-19T00:00:05.000Z",
|
|
834
|
+
syncMode: "all",
|
|
835
|
+
prefixSet: ["companies/indigo/"],
|
|
836
|
+
scopeChangeDetected: false,
|
|
837
|
+
orphansRemoved: 0,
|
|
838
|
+
orphansBlocked: 0,
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const result = await pullCompany({
|
|
844
|
+
ctx: makeCtx(),
|
|
845
|
+
journal,
|
|
846
|
+
hqRoot,
|
|
847
|
+
callerSub: "owner-sub",
|
|
848
|
+
scope: {
|
|
849
|
+
companyUid: "cmp_indigo",
|
|
850
|
+
syncMode: "shared",
|
|
851
|
+
prefixSet: ["companies/indigo/meetings/"],
|
|
852
|
+
strategy: "vend-fanout",
|
|
853
|
+
},
|
|
854
|
+
listFn: async () => [],
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
expect(result.pullRecord.scopeChangeDetected).toBe(false);
|
|
858
|
+
expect(result.pullRecord.orphansRemoved).toBe(0);
|
|
859
|
+
expect(fs.existsSync(legacyAbs)).toBe(true); // legacy file retained
|
|
860
|
+
});
|
|
861
|
+
|
|
744
862
|
it("GC's expired tombstones at the start of every leg", async () => {
|
|
745
863
|
const old = new Date(
|
|
746
864
|
Date.now() - 31 * 24 * 60 * 60 * 1000,
|
package/src/remote-pull.ts
CHANGED
|
@@ -368,6 +368,13 @@ export interface PullCompanyInput {
|
|
|
368
368
|
conflictKeys?: Set<string>;
|
|
369
369
|
/** Honor the operator override on dirty orphans (US-005 contract). */
|
|
370
370
|
forceScopeShrink?: boolean;
|
|
371
|
+
/**
|
|
372
|
+
* The caller's own Cognito `sub` for the scope-shrink authorship guard — a
|
|
373
|
+
* scope shrink never prunes content the caller authored. Defaults to the
|
|
374
|
+
* cached session sub (`resolveCallerSubFromCache()`); pass explicitly when
|
|
375
|
+
* the runner already decoded its idToken claims.
|
|
376
|
+
*/
|
|
377
|
+
callerSub?: string;
|
|
371
378
|
/** Listing override hook — see `ListRemoteForScopeInput.listFn`. */
|
|
372
379
|
listFn?: ListRemoteForScopeInput["listFn"];
|
|
373
380
|
vendForBatchFn?: ListRemoteForScopeInput["vendForBatchFn"];
|
|
@@ -432,6 +439,11 @@ export async function pullCompany(
|
|
|
432
439
|
hqRoot: input.hqRoot,
|
|
433
440
|
lastPrefixSet,
|
|
434
441
|
currentPrefixSet: input.scope.prefixSet,
|
|
442
|
+
callerSub: input.callerSub,
|
|
443
|
+
// Background runner pull: protect the caller's own work and don't make a
|
|
444
|
+
// destructive guess about unknown-author (legacy) orphans. The explicit
|
|
445
|
+
// `hq sync narrow` ritual is the confirmed path that opts out of this.
|
|
446
|
+
protectUnknownAuthors: true,
|
|
435
447
|
});
|
|
436
448
|
|
|
437
449
|
let scopeShrinkApplied: ApplyScopeShrinkResult | null = null;
|
package/src/scope-shrink.test.ts
CHANGED
|
@@ -239,6 +239,134 @@ describe("buildScopeShrinkPlan", () => {
|
|
|
239
239
|
});
|
|
240
240
|
expect(plan.orphans).toEqual([]);
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
// ── Authorship guard ──────────────────────────────────────────────────────
|
|
244
|
+
// Sync mode decides whether you mirror OTHER people's files; it must never
|
|
245
|
+
// orphan content the caller authored. Owners hold their whole vault by
|
|
246
|
+
// role-bypass, so without this guard a `shared`/`custom` scope would treat
|
|
247
|
+
// their own un-granted work as "someone else's file" and prune it.
|
|
248
|
+
|
|
249
|
+
it("never orphans a file the caller authored, even out of scope", () => {
|
|
250
|
+
const journal: SyncJournal = {
|
|
251
|
+
...emptyJournal(),
|
|
252
|
+
files: {
|
|
253
|
+
"companies/indigo/projects/mine.md": {
|
|
254
|
+
hash: "h",
|
|
255
|
+
size: 1,
|
|
256
|
+
syncedAt: "2026-05-01T00:00:00.000Z",
|
|
257
|
+
direction: "down",
|
|
258
|
+
createdBySub: "sub-corey",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
const plan = buildScopeShrinkPlan({
|
|
263
|
+
journal,
|
|
264
|
+
hqRoot,
|
|
265
|
+
lastPrefixSet: ["companies/indigo/"],
|
|
266
|
+
currentPrefixSet: ["companies/indigo/meetings/"],
|
|
267
|
+
callerSub: "sub-corey",
|
|
268
|
+
});
|
|
269
|
+
expect(plan.orphans).toEqual([]);
|
|
270
|
+
expect(plan.scopeChangeDetected).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("orphans a file authored by someone else (mode stops mirroring others' files)", () => {
|
|
274
|
+
const journal: SyncJournal = {
|
|
275
|
+
...emptyJournal(),
|
|
276
|
+
files: {
|
|
277
|
+
"companies/indigo/projects/theirs.md": {
|
|
278
|
+
hash: "h",
|
|
279
|
+
size: 1,
|
|
280
|
+
syncedAt: "2026-05-01T00:00:00.000Z",
|
|
281
|
+
direction: "down",
|
|
282
|
+
createdBySub: "sub-jacob",
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
const plan = buildScopeShrinkPlan({
|
|
287
|
+
journal,
|
|
288
|
+
hqRoot,
|
|
289
|
+
lastPrefixSet: ["companies/indigo/"],
|
|
290
|
+
currentPrefixSet: ["companies/indigo/meetings/"],
|
|
291
|
+
callerSub: "sub-corey",
|
|
292
|
+
protectUnknownAuthors: true,
|
|
293
|
+
});
|
|
294
|
+
expect(plan.orphans.map((o) => o.path)).toEqual([
|
|
295
|
+
"companies/indigo/projects/theirs.md",
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("retains an unknown-author orphan when protectUnknownAuthors is set (conservative auto path)", () => {
|
|
300
|
+
const journal: SyncJournal = {
|
|
301
|
+
...emptyJournal(),
|
|
302
|
+
files: {
|
|
303
|
+
"companies/indigo/projects/legacy.md": {
|
|
304
|
+
hash: "h",
|
|
305
|
+
size: 1,
|
|
306
|
+
syncedAt: "2026-05-01T00:00:00.000Z",
|
|
307
|
+
direction: "down",
|
|
308
|
+
// no createdBySub — a legacy entry predating author stamping
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
const plan = buildScopeShrinkPlan({
|
|
313
|
+
journal,
|
|
314
|
+
hqRoot,
|
|
315
|
+
lastPrefixSet: ["companies/indigo/"],
|
|
316
|
+
currentPrefixSet: ["companies/indigo/meetings/"],
|
|
317
|
+
callerSub: "sub-corey",
|
|
318
|
+
protectUnknownAuthors: true,
|
|
319
|
+
});
|
|
320
|
+
expect(plan.orphans).toEqual([]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("prunes an unknown-author orphan when protectUnknownAuthors is off (explicit narrow path)", () => {
|
|
324
|
+
const journal: SyncJournal = {
|
|
325
|
+
...emptyJournal(),
|
|
326
|
+
files: {
|
|
327
|
+
"companies/indigo/projects/legacy.md": {
|
|
328
|
+
hash: "h",
|
|
329
|
+
size: 1,
|
|
330
|
+
syncedAt: "2026-05-01T00:00:00.000Z",
|
|
331
|
+
direction: "down",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
const plan = buildScopeShrinkPlan({
|
|
336
|
+
journal,
|
|
337
|
+
hqRoot,
|
|
338
|
+
lastPrefixSet: ["companies/indigo/"],
|
|
339
|
+
currentPrefixSet: ["companies/indigo/meetings/"],
|
|
340
|
+
callerSub: "sub-corey",
|
|
341
|
+
});
|
|
342
|
+
expect(plan.orphans.map((o) => o.path)).toEqual([
|
|
343
|
+
"companies/indigo/projects/legacy.md",
|
|
344
|
+
]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("ignores authorship when no callerSub is supplied (back-compat)", () => {
|
|
348
|
+
const journal: SyncJournal = {
|
|
349
|
+
...emptyJournal(),
|
|
350
|
+
files: {
|
|
351
|
+
"companies/indigo/projects/mine.md": {
|
|
352
|
+
hash: "h",
|
|
353
|
+
size: 1,
|
|
354
|
+
syncedAt: "2026-05-01T00:00:00.000Z",
|
|
355
|
+
direction: "down",
|
|
356
|
+
createdBySub: "sub-corey",
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
const plan = buildScopeShrinkPlan({
|
|
361
|
+
journal,
|
|
362
|
+
hqRoot,
|
|
363
|
+
lastPrefixSet: ["companies/indigo/"],
|
|
364
|
+
currentPrefixSet: ["companies/indigo/meetings/"],
|
|
365
|
+
});
|
|
366
|
+
expect(plan.orphans.map((o) => o.path)).toEqual([
|
|
367
|
+
"companies/indigo/projects/mine.md",
|
|
368
|
+
]);
|
|
369
|
+
});
|
|
242
370
|
});
|
|
243
371
|
|
|
244
372
|
describe("applyScopeShrink", () => {
|
package/src/scope-shrink.ts
CHANGED
|
@@ -62,6 +62,26 @@ export interface BuildScopeShrinkPlanInput {
|
|
|
62
62
|
lastPrefixSet: string[];
|
|
63
63
|
/** Coalesced prefixes the CURRENT pull will use. */
|
|
64
64
|
currentPrefixSet: string[];
|
|
65
|
+
/**
|
|
66
|
+
* The caller's own Cognito `sub`. When set, a file the caller authored
|
|
67
|
+
* (`entry.createdBySub === callerSub`) is NEVER orphaned by a scope shrink —
|
|
68
|
+
* regardless of mode. This is the core of the authorship contract: sync mode
|
|
69
|
+
* governs whether you mirror *other people's* files; it must never disown
|
|
70
|
+
* your own work. Owners hold their whole vault by role-bypass, so without
|
|
71
|
+
* this guard a `shared`/`custom` scope would treat their own un-granted
|
|
72
|
+
* content as "someone else's file I happen to see" and prune it.
|
|
73
|
+
*/
|
|
74
|
+
callerSub?: string;
|
|
75
|
+
/**
|
|
76
|
+
* When `true`, an orphan whose authorship is unknown (`createdBySub`
|
|
77
|
+
* undefined — a legacy entry predating author stamping, or an object
|
|
78
|
+
* uploaded without author metadata) is also retained rather than pruned.
|
|
79
|
+
* The automatic background pull sets this so a routine sync never makes a
|
|
80
|
+
* destructive guess about pre-stamp content; the explicit `hq sync narrow`
|
|
81
|
+
* ritual (which carries its own confirmation + dirty gate) leaves it off so
|
|
82
|
+
* a deliberately-confirmed narrow can still reclaim legacy files.
|
|
83
|
+
*/
|
|
84
|
+
protectUnknownAuthors?: boolean;
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
/**
|
|
@@ -84,6 +104,7 @@ export function buildScopeShrinkPlan(
|
|
|
84
104
|
input: BuildScopeShrinkPlanInput,
|
|
85
105
|
): ScopeShrinkPlan {
|
|
86
106
|
const { journal, hqRoot, lastPrefixSet, currentPrefixSet } = input;
|
|
107
|
+
const { callerSub, protectUnknownAuthors } = input;
|
|
87
108
|
const orphans: OrphanClassification[] = [];
|
|
88
109
|
|
|
89
110
|
for (const [relPath, entry] of Object.entries(journal.files)) {
|
|
@@ -91,6 +112,14 @@ export function buildScopeShrinkPlan(
|
|
|
91
112
|
if (entry.direction !== "down") continue;
|
|
92
113
|
if (!isCoveredByAny(relPath, lastPrefixSet)) continue;
|
|
93
114
|
if (isCoveredByAny(relPath, currentPrefixSet)) continue;
|
|
115
|
+
// Authorship guard: sync mode decides whether you mirror OTHER people's
|
|
116
|
+
// files — it must never disown your own. A file the caller authored is
|
|
117
|
+
// sacred and never orphaned, even out of the current prefix scope. When
|
|
118
|
+
// `protectUnknownAuthors` is set (the automatic pull path), a legacy
|
|
119
|
+
// entry with no recorded author is also retained — a routine background
|
|
120
|
+
// sync should never make a destructive guess about pre-stamp content.
|
|
121
|
+
if (callerSub && entry.createdBySub === callerSub) continue;
|
|
122
|
+
if (protectUnknownAuthors && entry.createdBySub === undefined) continue;
|
|
94
123
|
orphans.push(classifyOrphan(relPath, entry, hqRoot));
|
|
95
124
|
}
|
|
96
125
|
|
package/src/types.ts
CHANGED
|
@@ -26,6 +26,18 @@ export interface JournalEntry {
|
|
|
26
26
|
size: number;
|
|
27
27
|
syncedAt: string;
|
|
28
28
|
direction: "up" | "down";
|
|
29
|
+
/**
|
|
30
|
+
* Cognito `sub` of the file's author, captured from the object's
|
|
31
|
+
* `created-by-sub` S3 user-metadata at download time (zero extra network —
|
|
32
|
+
* the GET response already carries it). Powers the scope-shrink authorship
|
|
33
|
+
* guard: a scope shrink must never orphan content the caller authored, so
|
|
34
|
+
* sync mode only ever governs whether you ALSO mirror *other people's*
|
|
35
|
+
* files. Optional for backwards compatibility — entries written before this
|
|
36
|
+
* field existed (or uploaded without author metadata) leave it `undefined`,
|
|
37
|
+
* which the automatic prune path treats conservatively (never auto-delete an
|
|
38
|
+
* unknown-author orphan).
|
|
39
|
+
*/
|
|
40
|
+
createdBySub?: string;
|
|
29
41
|
/**
|
|
30
42
|
* S3 ETag of the remote object as of last successful sync, normalized (no
|
|
31
43
|
* surrounding quotes). Optional for backwards compatibility: entries
|