@indigoai-us/hq-cloud 6.18.46-alpha.1 → 6.18.46
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 +11 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +18 -10
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +26 -14
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/share.d.ts +16 -2
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +192 -16
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/sync.d.ts +6 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +21 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/journal-row-store.d.ts +3 -0
- package/dist/journal-row-store.d.ts.map +1 -1
- package/dist/journal-row-store.js +23 -0
- package/dist/journal-row-store.js.map +1 -1
- package/dist/journal-snapshot-decoder.js +2 -0
- package/dist/journal-snapshot-decoder.js.map +1 -1
- package/dist/journal-snapshot-v4.d.ts +2 -2
- package/dist/journal-snapshot-v4.d.ts.map +1 -1
- package/dist/journal-snapshot-v4.js +8 -3
- package/dist/journal-snapshot-v4.js.map +1 -1
- package/dist/journal.d.ts +1 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +3 -1
- package/dist/journal.js.map +1 -1
- package/dist/s3.d.ts +22 -5
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +55 -10
- package/dist/s3.js.map +1 -1
- package/dist/symlink-target-policy.d.ts +32 -0
- package/dist/symlink-target-policy.d.ts.map +1 -0
- package/dist/symlink-target-policy.js +124 -0
- package/dist/symlink-target-policy.js.map +1 -0
- package/dist/sync/area-ledger-migration.d.ts +20 -0
- package/dist/sync/area-ledger-migration.d.ts.map +1 -1
- package/dist/sync/area-ledger-migration.js +59 -0
- package/dist/sync/area-ledger-migration.js.map +1 -1
- package/dist/sync/durable-apply.d.ts +16 -0
- package/dist/sync/durable-apply.d.ts.map +1 -1
- package/dist/sync/durable-apply.js +53 -0
- package/dist/sync/durable-apply.js.map +1 -1
- package/dist/sync/symlink-target-refusal-flag.d.ts +13 -0
- package/dist/sync/symlink-target-refusal-flag.d.ts.map +1 -0
- package/dist/sync/symlink-target-refusal-flag.js +51 -0
- package/dist/sync/symlink-target-refusal-flag.js.map +1 -0
- package/dist/sync-progress.d.ts.map +1 -1
- package/dist/sync-progress.js +2 -0
- package/dist/sync-progress.js.map +1 -1
- package/dist/telemetry.d.ts +4 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +1 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usage-company-fallback.d.ts +4 -3
- package/dist/usage-company-fallback.d.ts.map +1 -1
- package/dist/usage-company-fallback.js +40 -14
- package/dist/usage-company-fallback.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/share.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import * as os from "os";
|
|
9
9
|
import * as path from "path";
|
|
10
|
+
import { resolveSymlinkTargetRefusalFlag } from "../sync/symlink-target-refusal-flag.js";
|
|
11
|
+
import { inspectSymlinkTarget, materializedTargetMatchesWire } from "../symlink-target-policy.js";
|
|
10
12
|
import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
|
|
11
13
|
import { ensureCompanyVaultTransport } from "../company-vault-transport.js";
|
|
12
14
|
import { createSyncProgressRecorder } from "../sync-progress.js";
|
|
@@ -63,7 +65,7 @@ import { directoryFoldsCase, findCaseFoldedLocalKey, foldVaultKey, } from "./cas
|
|
|
63
65
|
* outcome. The name is derived from the relative path so concurrent probes
|
|
64
66
|
* for distinct files never collide on disk.
|
|
65
67
|
*/
|
|
66
|
-
async function remoteContentDiffers(ctx, relativePath, localHash, hqRoot) {
|
|
68
|
+
async function remoteContentDiffers(ctx, relativePath, localHash, hqRoot, symlinkOptions) {
|
|
67
69
|
const probeKey = crypto
|
|
68
70
|
.createHash("sha256")
|
|
69
71
|
.update(relativePath)
|
|
@@ -71,7 +73,7 @@ async function remoteContentDiffers(ctx, relativePath, localHash, hqRoot) {
|
|
|
71
73
|
.slice(0, 16);
|
|
72
74
|
const probePath = path.join(os.tmpdir(), `hq-conflict-probe-${readShortMachineId(hqRoot)}-${probeKey}.tmp`);
|
|
73
75
|
try {
|
|
74
|
-
await downloadFile(ctx, relativePath, probePath);
|
|
76
|
+
await downloadFile(ctx, relativePath, probePath, symlinkOptions);
|
|
75
77
|
const remoteHash = fs.lstatSync(probePath).isSymbolicLink()
|
|
76
78
|
? hashSymlinkTarget(fs.readlinkSync(probePath))
|
|
77
79
|
: hashFile(probePath);
|
|
@@ -273,6 +275,7 @@ export const _testing = {
|
|
|
273
275
|
cooperativeRowVisits: snapshotCooperativeRowVisits,
|
|
274
276
|
computePushPlan,
|
|
275
277
|
computePushPlanCooperatively,
|
|
278
|
+
normalizeWindowsSymlinkTarget,
|
|
276
279
|
};
|
|
277
280
|
/** Snapshot of `absolutePath`, or null when it is missing or not a plain file. */
|
|
278
281
|
function statSnapshot(absolutePath) {
|
|
@@ -446,7 +449,7 @@ function refusesServerOwnedPush(relativePath, ontologyEntityPush, absolutePath,
|
|
|
446
449
|
}
|
|
447
450
|
return isCloudAuthoritative(relativePath);
|
|
448
451
|
}
|
|
449
|
-
function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat, ontologyEntityPush = false) {
|
|
452
|
+
function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat, ontologyEntityPush = false, symlinkContext) {
|
|
450
453
|
const items = [];
|
|
451
454
|
let omittedSkips = 0;
|
|
452
455
|
let plannedRows = 0;
|
|
@@ -473,6 +476,18 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat, onto
|
|
|
473
476
|
// itself "looks" identical.
|
|
474
477
|
if (entry.kind === "symlink") {
|
|
475
478
|
const localHash = hashSymlinkTarget(entry.target);
|
|
479
|
+
const journalEntry = journalRows(journal).get(relativePath);
|
|
480
|
+
const portable = portablePushSymlinkTarget(absolutePath, relativePath, entry.target, journalEntry?.wireSymlinkTarget, symlinkContext);
|
|
481
|
+
if (portable.refused) {
|
|
482
|
+
items.push({
|
|
483
|
+
action: "refuse-unsafe-symlink",
|
|
484
|
+
absolutePath,
|
|
485
|
+
relativePath,
|
|
486
|
+
classification: portable.classification,
|
|
487
|
+
shape: portable.shape,
|
|
488
|
+
});
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
476
491
|
if (skipUnchanged) {
|
|
477
492
|
const existing = skipFingerprint(journal, relativePath);
|
|
478
493
|
// `!localDiverges`: see the regular-file gate below — a divergent entry
|
|
@@ -488,7 +503,7 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat, onto
|
|
|
488
503
|
kind: "symlink",
|
|
489
504
|
absolutePath,
|
|
490
505
|
relativePath,
|
|
491
|
-
target:
|
|
506
|
+
target: portable.target,
|
|
492
507
|
localHash,
|
|
493
508
|
size: 0,
|
|
494
509
|
});
|
|
@@ -608,12 +623,107 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat, onto
|
|
|
608
623
|
filesToSkip += omittedSkips;
|
|
609
624
|
return { items, filesToUpload, bytesToUpload, filesToSkip, preplannedFilesSkipped: omittedSkips };
|
|
610
625
|
}
|
|
626
|
+
function portablePushSymlinkTarget(absolutePath, relativePath, localTarget, wireTarget, context) {
|
|
627
|
+
const pathApi = process.platform === "win32" ? path.win32 : path;
|
|
628
|
+
let scope;
|
|
629
|
+
let scopeRoot;
|
|
630
|
+
let linkPath = relativePath;
|
|
631
|
+
if (context) {
|
|
632
|
+
const skillFarmRoot = pathApi.resolve(context.hqRoot, ".claude", "skills");
|
|
633
|
+
const skillFarmRelative = pathApi.relative(skillFarmRoot, pathApi.resolve(absolutePath));
|
|
634
|
+
const isSkillFarmPath = skillFarmRelative !== ".." &&
|
|
635
|
+
!skillFarmRelative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(skillFarmRelative);
|
|
636
|
+
if (isSkillFarmPath) {
|
|
637
|
+
scope = { kind: "personal" };
|
|
638
|
+
scopeRoot = context.hqRoot;
|
|
639
|
+
linkPath = `.claude/skills/${skillFarmRelative.split(pathApi.sep).join("/")}`;
|
|
640
|
+
}
|
|
641
|
+
else if (context.personalMode) {
|
|
642
|
+
scope = { kind: "personal" };
|
|
643
|
+
scopeRoot = context.syncRoot;
|
|
644
|
+
linkPath = `personal/${relativePath}`;
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
scope = { kind: "company", companySlug: context.companySlug };
|
|
648
|
+
scopeRoot = context.syncRoot;
|
|
649
|
+
linkPath = `companies/${context.companySlug}/${relativePath}`;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (wireTarget !== undefined && scope) {
|
|
653
|
+
let wireReport;
|
|
654
|
+
const wireInspection = inspectSymlinkTarget({
|
|
655
|
+
linkPath,
|
|
656
|
+
target: wireTarget,
|
|
657
|
+
scope,
|
|
658
|
+
refuse: true,
|
|
659
|
+
emit: (event) => { wireReport = event; },
|
|
660
|
+
});
|
|
661
|
+
if (wireInspection.refused) {
|
|
662
|
+
const event = wireReport ?? { classification: "escapes-scope", shape: "relative-path" };
|
|
663
|
+
process.stderr.write(`hq-cloud symlink target ${event.classification} ${event.shape}\n`);
|
|
664
|
+
return { target: "", refused: true, ...event };
|
|
665
|
+
}
|
|
666
|
+
if (materializedTargetMatchesWire(absolutePath, localTarget, wireTarget)) {
|
|
667
|
+
return { target: wireTarget, refused: false };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
let candidate = localTarget;
|
|
671
|
+
if (path.isAbsolute(localTarget) || path.win32.isAbsolute(localTarget)) {
|
|
672
|
+
if (!scope || !scopeRoot) {
|
|
673
|
+
const event = { classification: "windows-absolute", shape: "absolute-rooted" };
|
|
674
|
+
process.stderr.write(`hq-cloud symlink target ${event.classification} ${event.shape}\n`);
|
|
675
|
+
return { target: "", refused: true, ...event };
|
|
676
|
+
}
|
|
677
|
+
const absoluteTarget = pathApi.resolve(localTarget);
|
|
678
|
+
if (scope?.kind === "company" &&
|
|
679
|
+
relativePath === "knowledge" &&
|
|
680
|
+
absoluteTarget === pathApi.resolve(scopeRoot, `../../repos/private/knowledge-${scope.companySlug}`)) {
|
|
681
|
+
return { target: `../../repos/private/knowledge-${scope.companySlug}`, refused: false };
|
|
682
|
+
}
|
|
683
|
+
const relativeToScope = pathApi.relative(scopeRoot, absoluteTarget);
|
|
684
|
+
if (relativeToScope === ".." || relativeToScope.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(relativeToScope)) {
|
|
685
|
+
const event = { classification: "escapes-scope", shape: "absolute-outside-scope" };
|
|
686
|
+
process.stderr.write(`hq-cloud symlink target ${event.classification} ${event.shape}\n`);
|
|
687
|
+
return { target: "", refused: true, ...event };
|
|
688
|
+
}
|
|
689
|
+
candidate = pathApi.relative(pathApi.dirname(absolutePath), absoluteTarget).replaceAll("\\", "/");
|
|
690
|
+
}
|
|
691
|
+
else if (process.platform === "win32") {
|
|
692
|
+
// fs.readlink on Windows returns native separators for a relative link.
|
|
693
|
+
// The wire target is portable and must keep POSIX separators.
|
|
694
|
+
candidate = normalizeWindowsSymlinkTarget(candidate);
|
|
695
|
+
}
|
|
696
|
+
if (!scope && (path.isAbsolute(candidate) || path.win32.isAbsolute(candidate))) {
|
|
697
|
+
const event = { classification: "escapes-scope", shape: "absolute-outside-scope" };
|
|
698
|
+
process.stderr.write(`hq-cloud symlink target ${event.classification} ${event.shape}\n`);
|
|
699
|
+
return { target: "", refused: true, ...event };
|
|
700
|
+
}
|
|
701
|
+
if (!scope)
|
|
702
|
+
return { target: candidate, refused: false };
|
|
703
|
+
let report;
|
|
704
|
+
const inspection = inspectSymlinkTarget({
|
|
705
|
+
linkPath,
|
|
706
|
+
target: candidate,
|
|
707
|
+
scope,
|
|
708
|
+
refuse: true,
|
|
709
|
+
emit: (event) => { report = event; },
|
|
710
|
+
});
|
|
711
|
+
if (inspection.refused) {
|
|
712
|
+
const event = report ?? { classification: "escapes-scope", shape: "relative-path" };
|
|
713
|
+
process.stderr.write(`hq-cloud symlink target ${event.classification} ${event.shape}\n`);
|
|
714
|
+
return { target: "", refused: true, ...event };
|
|
715
|
+
}
|
|
716
|
+
return { target: candidate, refused: false };
|
|
717
|
+
}
|
|
718
|
+
function normalizeWindowsSymlinkTarget(target) {
|
|
719
|
+
return target.replaceAll("\\", "/");
|
|
720
|
+
}
|
|
611
721
|
/**
|
|
612
722
|
* Build a full-pass plan in bounded row batches. A scoped replan deliberately
|
|
613
723
|
* uses the synchronous single-row planner above, so it does not add an await
|
|
614
724
|
* boundary between upload admissions and collapse the transfer pool.
|
|
615
725
|
*/
|
|
616
|
-
async function computePushPlanCooperatively(filesToShare, journal, skipUnchanged, onHeartbeat, cooperativeCheckpoint, ontologyEntityPush = false) {
|
|
726
|
+
async function computePushPlanCooperatively(filesToShare, journal, skipUnchanged, onHeartbeat, cooperativeCheckpoint, ontologyEntityPush = false, symlinkContext) {
|
|
617
727
|
// Keep each synchronous hash/stat slice small enough that a large backlog
|
|
618
728
|
// cannot starve protocol heartbeats or watcher admission for tens of seconds.
|
|
619
729
|
// This changes only scheduling granularity; plan decisions are unchanged.
|
|
@@ -626,7 +736,7 @@ async function computePushPlanCooperatively(filesToShare, journal, skipUnchanged
|
|
|
626
736
|
const cooperativeWork = { rowsSinceYield: 0, lastYieldAt: 0 };
|
|
627
737
|
for (let start = 0; start < filesToShare.length; start += rowsPerPlanningYield) {
|
|
628
738
|
const slice = filesToShare.slice(start, start + rowsPerPlanningYield);
|
|
629
|
-
const plan = computePushPlan(slice, journal, skipUnchanged, onHeartbeat, ontologyEntityPush);
|
|
739
|
+
const plan = computePushPlan(slice, journal, skipUnchanged, onHeartbeat, ontologyEntityPush, symlinkContext);
|
|
630
740
|
items.push(...plan.items);
|
|
631
741
|
filesToUpload += plan.filesToUpload;
|
|
632
742
|
bytesToUpload += plan.bytesToUpload;
|
|
@@ -677,11 +787,11 @@ function recollectPushEntry(item, reuseKnownHash = true) {
|
|
|
677
787
|
* classified from the bytes and journal state that exist immediately before a
|
|
678
788
|
* worker starts, so callers never upload with a hash captured near pass entry.
|
|
679
789
|
*/
|
|
680
|
-
function replanPushItemAtExecution(item, journal, skipUnchanged, onHeartbeat, ontologyEntityPush = false, forceRehash = false) {
|
|
790
|
+
function replanPushItemAtExecution(item, journal, skipUnchanged, onHeartbeat, ontologyEntityPush = false, forceRehash = false, symlinkContext) {
|
|
681
791
|
const current = recollectPushEntry(item, !forceRehash);
|
|
682
792
|
if (current === null)
|
|
683
793
|
return null;
|
|
684
|
-
return computePushPlan([current], journal, skipUnchanged, onHeartbeat, ontologyEntityPush).items[0] ?? null;
|
|
794
|
+
return computePushPlan([current], journal, skipUnchanged, onHeartbeat, ontologyEntityPush, symlinkContext).items[0] ?? null;
|
|
685
795
|
}
|
|
686
796
|
function uploadBodyMatchesPlan(planned, current) {
|
|
687
797
|
if (planned.kind !== current.kind || planned.localHash !== current.localHash) {
|
|
@@ -1132,6 +1242,14 @@ async function createPushRunContext(options) {
|
|
|
1132
1242
|
? wrapFilterWithScope(transferFilter, syncRoot, options.prefixSet, onScopeExcluded)
|
|
1133
1243
|
: transferFilter;
|
|
1134
1244
|
const journalSlug = options.journalSlug ?? ctx.slug;
|
|
1245
|
+
const flagConfig = vaultConfig ?? privateFolderVaultConfig;
|
|
1246
|
+
const symlinkTargetRefusal = flagConfig
|
|
1247
|
+
? await resolveSymlinkTargetRefusalFlag({
|
|
1248
|
+
config: flagConfig,
|
|
1249
|
+
companyUid: ctx.uid,
|
|
1250
|
+
companySlug: ctx.slug,
|
|
1251
|
+
})
|
|
1252
|
+
: false;
|
|
1135
1253
|
if (journalSlug === PERSONAL_VAULT_JOURNAL_SLUG)
|
|
1136
1254
|
migratePersonalVaultJournal();
|
|
1137
1255
|
// The scoped runner has not completed its narrow filesystem walk yet, so
|
|
@@ -1144,6 +1262,7 @@ async function createPushRunContext(options) {
|
|
|
1144
1262
|
ontologyEntityPush: false,
|
|
1145
1263
|
skillRegisterLandedBytes: false,
|
|
1146
1264
|
personalConnectorSettingsSync,
|
|
1265
|
+
symlinkTargetRefusal,
|
|
1147
1266
|
skillRegistrationFallbacks: 0,
|
|
1148
1267
|
paths,
|
|
1149
1268
|
message,
|
|
@@ -1575,7 +1694,11 @@ async function reconcileOnePendingCompanySkill(run, entry) {
|
|
|
1575
1694
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-skill-metadata-"));
|
|
1576
1695
|
const tempFile = path.join(tempDir, "SKILL.md");
|
|
1577
1696
|
try {
|
|
1578
|
-
await downloadFile(run.ctx, entry.relativePath, tempFile
|
|
1697
|
+
await downloadFile(run.ctx, entry.relativePath, tempFile, {
|
|
1698
|
+
refuseUnsafeSymlinkTargets: run.symlinkTargetRefusal,
|
|
1699
|
+
symlinkTargetScope: { kind: "company", companySlug: run.ctx.slug },
|
|
1700
|
+
symlinkTargetLinkPath: `companies/${run.ctx.slug}/${entry.relativePath}`,
|
|
1701
|
+
});
|
|
1579
1702
|
// The reconciled bytes are the REMOTE object (a peer replaced the one
|
|
1580
1703
|
// this marker described), not the local file, so pass no absolutePath:
|
|
1581
1704
|
// a 409 here is recorded, not forked (see reconcileLandedCompanySkill).
|
|
@@ -1928,7 +2051,7 @@ async function buildSharePlans(run) {
|
|
|
1928
2051
|
if (pushEntries.some((entry) => isOntologyEntityKey(entry.relativePath))) {
|
|
1929
2052
|
run.ontologyEntityPush = await resolveRunOntologyEntityPush(run);
|
|
1930
2053
|
}
|
|
1931
|
-
const pushPlan = await computePushPlanCooperatively(pushEntries, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.options.cooperativeCheckpoint, run.ontologyEntityPush);
|
|
2054
|
+
const pushPlan = await computePushPlanCooperatively(pushEntries, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.options.cooperativeCheckpoint, run.ontologyEntityPush, { hqRoot: run.hqRoot, syncRoot: run.syncRoot, personalMode: run.options.personalMode === true, companySlug: run.ctx.slug });
|
|
1932
2055
|
const deletePlan = run.propagateDeletes === true
|
|
1933
2056
|
? await computeDeletePlan(run.journal, run.syncRoot, deleteScopeRoots, run.shouldSync, run.propagateDeletePolicy, run.ctx, run.options.personalMode !== true, run.skipSecretPaths)
|
|
1934
2057
|
: { toDelete: [], toTombstone: [], refusedStale: [] };
|
|
@@ -2157,7 +2280,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
2157
2280
|
for (const previewItem of pushPlan.items) {
|
|
2158
2281
|
if (previewItem.action !== "upload")
|
|
2159
2282
|
continue;
|
|
2160
|
-
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush);
|
|
2283
|
+
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush, false, { hqRoot: run.hqRoot, syncRoot: run.syncRoot, personalMode: run.options.personalMode === true, companySlug: run.ctx.slug });
|
|
2161
2284
|
if (item?.action === "upload" && !isUploadSuppressedByTombstone(item)) {
|
|
2162
2285
|
plannedUploadCandidates.push(item);
|
|
2163
2286
|
}
|
|
@@ -2252,6 +2375,14 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
2252
2375
|
emitServerOwnedRefusal(run, item.relativePath);
|
|
2253
2376
|
}
|
|
2254
2377
|
}
|
|
2378
|
+
else if (item.action === "refuse-unsafe-symlink") {
|
|
2379
|
+
run.emit({
|
|
2380
|
+
type: "skip-unsafe-symlink-target",
|
|
2381
|
+
path: item.relativePath,
|
|
2382
|
+
classification: item.classification,
|
|
2383
|
+
shape: item.shape,
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2255
2386
|
else if (item.action === "skip-size-limit") {
|
|
2256
2387
|
// A file over the size cap is a permanent, benign skip — NOT an error.
|
|
2257
2388
|
let bytes = 0;
|
|
@@ -2299,7 +2430,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
2299
2430
|
return;
|
|
2300
2431
|
const { absolutePath, relativePath, localHash } = item;
|
|
2301
2432
|
const refreshUploadItem = () => {
|
|
2302
|
-
const refreshed = replanPushItemAtExecution(item, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush, true);
|
|
2433
|
+
const refreshed = replanPushItemAtExecution(item, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush, true, { hqRoot: run.hqRoot, syncRoot: run.syncRoot, personalMode: run.options.personalMode === true, companySlug: run.ctx.slug });
|
|
2303
2434
|
if (refreshed === null) {
|
|
2304
2435
|
counters.filesSkipped++;
|
|
2305
2436
|
return null;
|
|
@@ -2514,10 +2645,33 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
2514
2645
|
let isFreshCollision = false;
|
|
2515
2646
|
let freshContentConverged = false;
|
|
2516
2647
|
if (!journalEntry && item.kind === "symlink") {
|
|
2648
|
+
const remoteTarget = await readVerifiedRemoteSymlinkTarget(run.ctx, relativePath);
|
|
2649
|
+
const localTarget = fs.readlinkSync(absolutePath);
|
|
2650
|
+
if (remoteTarget !== null && materializedTargetMatchesWire(absolutePath, localTarget, remoteTarget)) {
|
|
2651
|
+
const localStat = fs.lstatSync(absolutePath);
|
|
2652
|
+
updateEntry(run.journal, relativePath, hashSymlinkTarget(localTarget), 0, "down", absolutePath, {
|
|
2653
|
+
remoteEtag: remoteMeta.etag,
|
|
2654
|
+
mtimeMs: localStat.mtimeMs,
|
|
2655
|
+
ctimeMs: localStat.ctimeMs,
|
|
2656
|
+
kind: "symlink",
|
|
2657
|
+
wireSymlinkTarget: remoteTarget,
|
|
2658
|
+
});
|
|
2659
|
+
flushShareJournal(run);
|
|
2660
|
+
counters.filesSkipped++;
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2517
2663
|
isFreshCollision = true;
|
|
2518
2664
|
}
|
|
2519
2665
|
else if (!journalEntry && item.kind === "file") {
|
|
2520
|
-
const remoteDiffers = await remoteContentDiffers(run.ctx, relativePath, localHash, run.hqRoot
|
|
2666
|
+
const remoteDiffers = await remoteContentDiffers(run.ctx, relativePath, localHash, run.hqRoot, {
|
|
2667
|
+
refuseUnsafeSymlinkTargets: run.symlinkTargetRefusal,
|
|
2668
|
+
symlinkTargetScope: run.options.personalMode === true
|
|
2669
|
+
? { kind: "personal" }
|
|
2670
|
+
: { kind: "company", companySlug: run.ctx.slug },
|
|
2671
|
+
symlinkTargetLinkPath: run.options.personalMode === true
|
|
2672
|
+
? `personal/${relativePath}`
|
|
2673
|
+
: `companies/${run.ctx.slug}/${relativePath}`,
|
|
2674
|
+
});
|
|
2521
2675
|
if (remoteDiffers) {
|
|
2522
2676
|
isFreshCollision = true;
|
|
2523
2677
|
}
|
|
@@ -2831,6 +2985,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
2831
2985
|
mtimeMs,
|
|
2832
2986
|
ctimeMs,
|
|
2833
2987
|
kind: item.kind,
|
|
2988
|
+
...(item.kind === "symlink" ? { wireSymlinkTarget: item.target } : {}),
|
|
2834
2989
|
skillMetadataPending: skillContentAtPut !== undefined &&
|
|
2835
2990
|
skillRegistrarForRun(run) !== undefined,
|
|
2836
2991
|
});
|
|
@@ -3153,7 +3308,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
3153
3308
|
continue;
|
|
3154
3309
|
}
|
|
3155
3310
|
const previewItem = queue[0];
|
|
3156
|
-
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush);
|
|
3311
|
+
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat, run.ontologyEntityPush, false, { hqRoot: run.hqRoot, syncRoot: run.syncRoot, personalMode: run.options.personalMode === true, companySlug: run.ctx.slug });
|
|
3157
3312
|
if (item === null) {
|
|
3158
3313
|
queue.shift();
|
|
3159
3314
|
counters.filesSkipped++;
|
|
@@ -3330,7 +3485,16 @@ async function probeRemoteForWinner(run, item, remoteMtime) {
|
|
|
3330
3485
|
let decision;
|
|
3331
3486
|
try {
|
|
3332
3487
|
fs.mkdirSync(path.dirname(probeAbs), { recursive: true });
|
|
3333
|
-
const downloaded = await downloadFile(run.ctx, item.relativePath, probeAbs,
|
|
3488
|
+
const downloaded = await downloadFile(run.ctx, item.relativePath, probeAbs, {
|
|
3489
|
+
...(run.options.transferPriority === "realtime" ? { bypassBandwidthGovernor: true } : {}),
|
|
3490
|
+
refuseUnsafeSymlinkTargets: run.symlinkTargetRefusal,
|
|
3491
|
+
symlinkTargetScope: run.options.personalMode === true
|
|
3492
|
+
? { kind: "personal" }
|
|
3493
|
+
: { kind: "company", companySlug: run.ctx.slug },
|
|
3494
|
+
symlinkTargetLinkPath: run.options.personalMode === true
|
|
3495
|
+
? `personal/${item.relativePath}`
|
|
3496
|
+
: `companies/${run.ctx.slug}/${item.relativePath}`,
|
|
3497
|
+
});
|
|
3334
3498
|
fetched = { abs: probeAbs, downloaded };
|
|
3335
3499
|
if (item.kind === "file" && !fs.lstatSync(probeAbs).isSymbolicLink()) {
|
|
3336
3500
|
const localStat = fs.lstatSync(item.absolutePath);
|
|
@@ -3451,7 +3615,16 @@ async function adoptRemoteOnPush(run, item, remoteHash, decision, probe) {
|
|
|
3451
3615
|
downloaded = probeBody.downloaded;
|
|
3452
3616
|
}
|
|
3453
3617
|
else {
|
|
3454
|
-
downloaded = await downloadFile(run.ctx, item.relativePath, item.absolutePath,
|
|
3618
|
+
downloaded = await downloadFile(run.ctx, item.relativePath, item.absolutePath, {
|
|
3619
|
+
...(run.options.transferPriority === "realtime" ? { bypassBandwidthGovernor: true } : {}),
|
|
3620
|
+
refuseUnsafeSymlinkTargets: run.symlinkTargetRefusal,
|
|
3621
|
+
symlinkTargetScope: run.options.personalMode === true
|
|
3622
|
+
? { kind: "personal" }
|
|
3623
|
+
: { kind: "company", companySlug: run.ctx.slug },
|
|
3624
|
+
symlinkTargetLinkPath: run.options.personalMode === true
|
|
3625
|
+
? `personal/${item.relativePath}`
|
|
3626
|
+
: `companies/${run.ctx.slug}/${item.relativePath}`,
|
|
3627
|
+
});
|
|
3455
3628
|
}
|
|
3456
3629
|
remoteAdopted = true;
|
|
3457
3630
|
const localLstat = fs.lstatSync(item.absolutePath);
|
|
@@ -3469,6 +3642,9 @@ async function adoptRemoteOnPush(run, item, remoteHash, decision, probe) {
|
|
|
3469
3642
|
ctimeMs: localLstat.ctimeMs,
|
|
3470
3643
|
...(createdBySub !== undefined ? { createdBySub } : {}),
|
|
3471
3644
|
kind: isLocalSymlink ? "symlink" : "file",
|
|
3645
|
+
...(isLocalSymlink && downloaded?.symlinkTarget !== undefined
|
|
3646
|
+
? { wireSymlinkTarget: downloaded.symlinkTarget }
|
|
3647
|
+
: {}),
|
|
3472
3648
|
});
|
|
3473
3649
|
recordPushConflict(run, item, {
|
|
3474
3650
|
detectedAt,
|