@ricsam/r5d-worker 0.0.58 → 0.0.60
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/README.md +1 -1
- package/dist/cjs/main.cjs +569 -191
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-convergence.cjs +256 -0
- package/dist/cjs/workspace-incident-state.cjs +32 -3
- package/dist/cjs/workspace-manifest-admission.cjs +64 -0
- package/dist/cjs/workspace-sync.cjs +129 -16
- package/dist/mjs/main.mjs +579 -197
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-convergence.mjs +213 -0
- package/dist/mjs/workspace-incident-state.mjs +31 -3
- package/dist/mjs/workspace-manifest-admission.mjs +30 -0
- package/dist/mjs/workspace-sync.mjs +126 -15
- package/dist/types/workspace-convergence.d.ts +82 -0
- package/dist/types/workspace-incident-state.d.ts +15 -1
- package/dist/types/workspace-manifest-admission.d.ts +24 -0
- package/dist/types/workspace-sync.d.ts +23 -6
- package/package.json +1 -1
- package/dist/cjs/workspace-sync-fast-path.cjs +0 -108
- package/dist/mjs/workspace-sync-fast-path.mjs +0 -81
- package/dist/types/workspace-sync-fast-path.d.ts +0 -69
|
@@ -2,8 +2,8 @@ import { type CheckoutPathSegments } from "./managed-paths";
|
|
|
2
2
|
import { type WorkspaceMutationLease } from "./workspace-mutation-gate";
|
|
3
3
|
export declare const WORKSPACE_BRANCH = "main";
|
|
4
4
|
export declare const WORKSPACE_INTENT_REF_PREFIX = "refs/r5d/workspace-intents/";
|
|
5
|
+
export declare const WORKSPACE_PUBLICATION_REF_PREFIX = "refs/r5d/workspace-publications/";
|
|
5
6
|
export declare const MAX_WORKSPACE_SYNC_DIFF_BYTES: number;
|
|
6
|
-
export declare const WORKSPACE_PERIODIC_SCAN_INTERVAL_MS = 5000;
|
|
7
7
|
export type WorkspaceProjectManifestEntry = {
|
|
8
8
|
projectId: string;
|
|
9
9
|
checkoutPathSegments: CheckoutPathSegments;
|
|
@@ -51,17 +51,13 @@ export type WorkspaceSyncResult = {
|
|
|
51
51
|
affectedPaths: string[];
|
|
52
52
|
discardedPaths: string[];
|
|
53
53
|
localChangesDiscarded: boolean;
|
|
54
|
+
dirtyGeneration?: number;
|
|
54
55
|
sampledCanonicalCheckoutTreeHash?: string;
|
|
55
56
|
telemetry?: {
|
|
56
57
|
totalMs: number;
|
|
57
58
|
queueMs: number;
|
|
58
59
|
prepareMs: number;
|
|
59
60
|
synchronizeMs: number;
|
|
60
|
-
fastPath: {
|
|
61
|
-
hit: boolean;
|
|
62
|
-
reason: string;
|
|
63
|
-
proofAgeMs?: number;
|
|
64
|
-
};
|
|
65
61
|
};
|
|
66
62
|
error?: string;
|
|
67
63
|
};
|
|
@@ -80,6 +76,8 @@ export type WorkspaceSyncInput = {
|
|
|
80
76
|
* Workers never publish the canonical workspace branch directly.
|
|
81
77
|
*/
|
|
82
78
|
quarantineRef?: string;
|
|
79
|
+
/** Exact canonical head fenced by publication authority. */
|
|
80
|
+
expectedCanonicalHead?: string | null;
|
|
83
81
|
confirmedLargeDiff?: boolean;
|
|
84
82
|
confirmationReason?: string;
|
|
85
83
|
resetToCanonical?: boolean;
|
|
@@ -124,6 +122,25 @@ export declare function mirrorVisibleWorkspaceToShadow(input: WorkspaceSyncInput
|
|
|
124
122
|
export declare function mirrorShadowWorkspaceToVisible(input: WorkspaceSyncInput, opaqueWorkspaceRoots?: readonly string[], hydrationPreimages?: VisibleHydrationPreimages): void;
|
|
125
123
|
export declare function assertWorkspaceQuarantineRef(quarantineRef: string): void;
|
|
126
124
|
export declare function synchronizeWorkspace(rawInput: WorkspaceSyncInput): Promise<WorkspaceSyncResult>;
|
|
125
|
+
export type WorkspaceHeadConvergenceResult = {
|
|
126
|
+
localHead: string | null;
|
|
127
|
+
canonicalHead: string | null;
|
|
128
|
+
localChanges: boolean;
|
|
129
|
+
hydrated: boolean;
|
|
130
|
+
hydrationConflicts: string[];
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Fetches the newest canonical head without treating an inbound overlap as an
|
|
134
|
+
* incident. A busy caller passes hydrateVisible=false, which updates only the
|
|
135
|
+
* remote-tracking ref. An idle caller may hydrate only after proving that its
|
|
136
|
+
* visible tree still matches the local workspace head.
|
|
137
|
+
*/
|
|
138
|
+
export declare function convergeWorkspaceHead(rawInput: WorkspaceSyncInput, options: {
|
|
139
|
+
hydrateVisible: boolean;
|
|
140
|
+
}): Promise<WorkspaceHeadConvergenceResult>;
|
|
141
|
+
/** Returns a local-only fingerprint. Unlike the legacy periodic observation it
|
|
142
|
+
* deliberately ignores whether origin/main has advanced. */
|
|
143
|
+
export declare function calculateWorkspaceLocalDiffFingerprint(rawInput: WorkspaceSyncInput): string;
|
|
127
144
|
export declare function calculateWorkspaceDiffFingerprint(rawInput: WorkspaceSyncInput): string;
|
|
128
145
|
export declare class WorkspaceSyncSingleFlight {
|
|
129
146
|
private readonly mutationGate;
|
package/package.json
CHANGED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var workspace_sync_fast_path_exports = {};
|
|
20
|
-
__export(workspace_sync_fast_path_exports, {
|
|
21
|
-
RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS: () => RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS,
|
|
22
|
-
RecentWorkspaceSyncProofCache: () => RecentWorkspaceSyncProofCache,
|
|
23
|
-
readShadowWorkspaceCanonicalHead: () => readShadowWorkspaceCanonicalHead,
|
|
24
|
-
workspaceSyncScopeKey: () => workspaceSyncScopeKey
|
|
25
|
-
});
|
|
26
|
-
module.exports = __toCommonJS(workspace_sync_fast_path_exports);
|
|
27
|
-
var import_node_crypto = require("node:crypto");
|
|
28
|
-
const RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS = 15e3;
|
|
29
|
-
function stableProjectShape(project) {
|
|
30
|
-
return {
|
|
31
|
-
projectId: project.projectId,
|
|
32
|
-
checkoutPathSegments: [...project.checkoutPathSegments],
|
|
33
|
-
projectPath: project.projectPath,
|
|
34
|
-
repoHttpUrl: project.repoHttpUrl,
|
|
35
|
-
defaultBranch: project.defaultBranch,
|
|
36
|
-
branches: [...project.branches].sort(),
|
|
37
|
-
canonicalCheckouts: [...project.canonicalCheckouts].sort(
|
|
38
|
-
(left, right) => left.branchName.localeCompare(right.branchName) || left.scaffoldCommitHash.localeCompare(right.scaffoldCommitHash)
|
|
39
|
-
)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function workspaceSyncScopeKey(remoteUrl, projects) {
|
|
43
|
-
const stableProjects = [...projects].map(stableProjectShape).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.projectPath.localeCompare(right.projectPath));
|
|
44
|
-
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify({ remoteUrl, projects: stableProjects })).digest("hex");
|
|
45
|
-
}
|
|
46
|
-
function readShadowWorkspaceCanonicalHead(shadowRoot) {
|
|
47
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--verify", "HEAD"], {
|
|
48
|
-
cwd: shadowRoot,
|
|
49
|
-
stdout: "pipe",
|
|
50
|
-
stderr: "ignore"
|
|
51
|
-
});
|
|
52
|
-
if (result.exitCode !== 0) return null;
|
|
53
|
-
const head = result.stdout.toString().trim();
|
|
54
|
-
return /^[0-9a-f]{40,64}$/.test(head) ? head : null;
|
|
55
|
-
}
|
|
56
|
-
class RecentWorkspaceSyncProofCache {
|
|
57
|
-
constructor(maxAgeMs = RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS) {
|
|
58
|
-
this.maxAgeMs = maxAgeMs;
|
|
59
|
-
}
|
|
60
|
-
maxAgeMs;
|
|
61
|
-
generation = 0;
|
|
62
|
-
proof = null;
|
|
63
|
-
beginObservation() {
|
|
64
|
-
return this.generation;
|
|
65
|
-
}
|
|
66
|
-
invalidate() {
|
|
67
|
-
this.generation += 1;
|
|
68
|
-
}
|
|
69
|
-
recordStable(input) {
|
|
70
|
-
if (input.observationGeneration !== this.generation || input.workerBusy || !input.canonicalHead || !/^[0-9a-f]{40,64}$/.test(input.canonicalHead)) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
this.proof = {
|
|
74
|
-
canonicalHead: input.canonicalHead,
|
|
75
|
-
scopeKey: input.scopeKey,
|
|
76
|
-
observedAtMs: input.observedAtMs,
|
|
77
|
-
generation: this.generation
|
|
78
|
-
};
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
evaluate(input) {
|
|
82
|
-
if (!input.allowRecentNoChangeFastPath || input.trigger.type !== "inbound_head") {
|
|
83
|
-
return { hit: false, reason: "unsupported_trigger" };
|
|
84
|
-
}
|
|
85
|
-
if (input.trigger.canonicalCheckoutOnly) return { hit: false, reason: "canonical_checkout" };
|
|
86
|
-
if (input.skipVisibleMirror) return { hit: false, reason: "visible_mirror_skipped" };
|
|
87
|
-
if (input.resetToCanonical) return { hit: false, reason: "reset_requested" };
|
|
88
|
-
if (input.incidentActive) return { hit: false, reason: "incident_active" };
|
|
89
|
-
if (input.workerBusy) return { hit: false, reason: "worker_busy" };
|
|
90
|
-
if (input.pendingCheckouts) return { hit: false, reason: "pending_checkouts" };
|
|
91
|
-
if (!input.canonicalHead) return { hit: false, reason: "canonical_head_missing" };
|
|
92
|
-
const proof = this.proof;
|
|
93
|
-
if (!proof) return { hit: false, reason: "no_recent_proof" };
|
|
94
|
-
if (proof.generation !== this.generation) return { hit: false, reason: "proof_invalidated" };
|
|
95
|
-
const proofAgeMs = Math.max(0, input.nowMs - proof.observedAtMs);
|
|
96
|
-
if (proofAgeMs > this.maxAgeMs) return { hit: false, reason: "proof_expired" };
|
|
97
|
-
if (proof.scopeKey !== input.scopeKey) return { hit: false, reason: "scope_changed" };
|
|
98
|
-
if (proof.canonicalHead !== input.canonicalHead) return { hit: false, reason: "canonical_head_changed" };
|
|
99
|
-
return { hit: true, reason: "recent_stable_no_change", canonicalHead: proof.canonicalHead, proofAgeMs };
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
103
|
-
0 && (module.exports = {
|
|
104
|
-
RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS,
|
|
105
|
-
RecentWorkspaceSyncProofCache,
|
|
106
|
-
readShadowWorkspaceCanonicalHead,
|
|
107
|
-
workspaceSyncScopeKey
|
|
108
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
const RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS = 15e3;
|
|
3
|
-
function stableProjectShape(project) {
|
|
4
|
-
return {
|
|
5
|
-
projectId: project.projectId,
|
|
6
|
-
checkoutPathSegments: [...project.checkoutPathSegments],
|
|
7
|
-
projectPath: project.projectPath,
|
|
8
|
-
repoHttpUrl: project.repoHttpUrl,
|
|
9
|
-
defaultBranch: project.defaultBranch,
|
|
10
|
-
branches: [...project.branches].sort(),
|
|
11
|
-
canonicalCheckouts: [...project.canonicalCheckouts].sort(
|
|
12
|
-
(left, right) => left.branchName.localeCompare(right.branchName) || left.scaffoldCommitHash.localeCompare(right.scaffoldCommitHash)
|
|
13
|
-
)
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function workspaceSyncScopeKey(remoteUrl, projects) {
|
|
17
|
-
const stableProjects = [...projects].map(stableProjectShape).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.projectPath.localeCompare(right.projectPath));
|
|
18
|
-
return createHash("sha256").update(JSON.stringify({ remoteUrl, projects: stableProjects })).digest("hex");
|
|
19
|
-
}
|
|
20
|
-
function readShadowWorkspaceCanonicalHead(shadowRoot) {
|
|
21
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--verify", "HEAD"], {
|
|
22
|
-
cwd: shadowRoot,
|
|
23
|
-
stdout: "pipe",
|
|
24
|
-
stderr: "ignore"
|
|
25
|
-
});
|
|
26
|
-
if (result.exitCode !== 0) return null;
|
|
27
|
-
const head = result.stdout.toString().trim();
|
|
28
|
-
return /^[0-9a-f]{40,64}$/.test(head) ? head : null;
|
|
29
|
-
}
|
|
30
|
-
class RecentWorkspaceSyncProofCache {
|
|
31
|
-
constructor(maxAgeMs = RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS) {
|
|
32
|
-
this.maxAgeMs = maxAgeMs;
|
|
33
|
-
}
|
|
34
|
-
maxAgeMs;
|
|
35
|
-
generation = 0;
|
|
36
|
-
proof = null;
|
|
37
|
-
beginObservation() {
|
|
38
|
-
return this.generation;
|
|
39
|
-
}
|
|
40
|
-
invalidate() {
|
|
41
|
-
this.generation += 1;
|
|
42
|
-
}
|
|
43
|
-
recordStable(input) {
|
|
44
|
-
if (input.observationGeneration !== this.generation || input.workerBusy || !input.canonicalHead || !/^[0-9a-f]{40,64}$/.test(input.canonicalHead)) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
this.proof = {
|
|
48
|
-
canonicalHead: input.canonicalHead,
|
|
49
|
-
scopeKey: input.scopeKey,
|
|
50
|
-
observedAtMs: input.observedAtMs,
|
|
51
|
-
generation: this.generation
|
|
52
|
-
};
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
evaluate(input) {
|
|
56
|
-
if (!input.allowRecentNoChangeFastPath || input.trigger.type !== "inbound_head") {
|
|
57
|
-
return { hit: false, reason: "unsupported_trigger" };
|
|
58
|
-
}
|
|
59
|
-
if (input.trigger.canonicalCheckoutOnly) return { hit: false, reason: "canonical_checkout" };
|
|
60
|
-
if (input.skipVisibleMirror) return { hit: false, reason: "visible_mirror_skipped" };
|
|
61
|
-
if (input.resetToCanonical) return { hit: false, reason: "reset_requested" };
|
|
62
|
-
if (input.incidentActive) return { hit: false, reason: "incident_active" };
|
|
63
|
-
if (input.workerBusy) return { hit: false, reason: "worker_busy" };
|
|
64
|
-
if (input.pendingCheckouts) return { hit: false, reason: "pending_checkouts" };
|
|
65
|
-
if (!input.canonicalHead) return { hit: false, reason: "canonical_head_missing" };
|
|
66
|
-
const proof = this.proof;
|
|
67
|
-
if (!proof) return { hit: false, reason: "no_recent_proof" };
|
|
68
|
-
if (proof.generation !== this.generation) return { hit: false, reason: "proof_invalidated" };
|
|
69
|
-
const proofAgeMs = Math.max(0, input.nowMs - proof.observedAtMs);
|
|
70
|
-
if (proofAgeMs > this.maxAgeMs) return { hit: false, reason: "proof_expired" };
|
|
71
|
-
if (proof.scopeKey !== input.scopeKey) return { hit: false, reason: "scope_changed" };
|
|
72
|
-
if (proof.canonicalHead !== input.canonicalHead) return { hit: false, reason: "canonical_head_changed" };
|
|
73
|
-
return { hit: true, reason: "recent_stable_no_change", canonicalHead: proof.canonicalHead, proofAgeMs };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export {
|
|
77
|
-
RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS,
|
|
78
|
-
RecentWorkspaceSyncProofCache,
|
|
79
|
-
readShadowWorkspaceCanonicalHead,
|
|
80
|
-
workspaceSyncScopeKey
|
|
81
|
-
};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export declare const RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS = 15000;
|
|
2
|
-
export type WorkspaceSyncFastPathMissReason = "unsupported_trigger" | "canonical_checkout" | "visible_mirror_skipped" | "reset_requested" | "incident_active" | "worker_busy" | "pending_checkouts" | "canonical_head_missing" | "no_recent_proof" | "proof_invalidated" | "proof_expired" | "scope_changed" | "canonical_head_changed";
|
|
3
|
-
export type WorkspaceSyncFastPathDecision = {
|
|
4
|
-
hit: true;
|
|
5
|
-
reason: "recent_stable_no_change";
|
|
6
|
-
canonicalHead: string;
|
|
7
|
-
proofAgeMs: number;
|
|
8
|
-
} | {
|
|
9
|
-
hit: false;
|
|
10
|
-
reason: WorkspaceSyncFastPathMissReason;
|
|
11
|
-
};
|
|
12
|
-
type WorkspaceSyncScopeProject = {
|
|
13
|
-
projectId: string;
|
|
14
|
-
checkoutPathSegments: readonly string[];
|
|
15
|
-
projectPath: string;
|
|
16
|
-
repoHttpUrl: string | null;
|
|
17
|
-
defaultBranch: string;
|
|
18
|
-
branches: readonly string[];
|
|
19
|
-
canonicalCheckouts: ReadonlyArray<{
|
|
20
|
-
branchName: string;
|
|
21
|
-
scaffoldCommitHash: string;
|
|
22
|
-
}>;
|
|
23
|
-
};
|
|
24
|
-
type WorkspaceSyncFastPathInput = {
|
|
25
|
-
trigger: {
|
|
26
|
-
type: string;
|
|
27
|
-
canonicalCheckoutOnly?: boolean;
|
|
28
|
-
};
|
|
29
|
-
allowRecentNoChangeFastPath: boolean;
|
|
30
|
-
canonicalHead?: string | null;
|
|
31
|
-
scopeKey: string;
|
|
32
|
-
nowMs: number;
|
|
33
|
-
skipVisibleMirror?: boolean;
|
|
34
|
-
resetToCanonical?: boolean;
|
|
35
|
-
incidentActive: boolean;
|
|
36
|
-
workerBusy: boolean;
|
|
37
|
-
pendingCheckouts: boolean;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Fingerprints the inputs that determine which visible paths participate in a
|
|
41
|
-
* generic workspace scan. Authentication headers are deliberately excluded.
|
|
42
|
-
*/
|
|
43
|
-
export declare function workspaceSyncScopeKey(remoteUrl: string, projects: readonly WorkspaceSyncScopeProject[]): string;
|
|
44
|
-
export declare function readShadowWorkspaceCanonicalHead(shadowRoot: string): string | null;
|
|
45
|
-
/**
|
|
46
|
-
* A short-lived proof cache for the duplicate scan between the worker's
|
|
47
|
-
* periodic stable-fingerprint pass and an immediately following agent start.
|
|
48
|
-
*
|
|
49
|
-
* The proof is process/connection local. Callers invalidate it before every
|
|
50
|
-
* known local mutation and retain an observation generation while a scan is
|
|
51
|
-
* running, so a mutation racing the scan cannot publish a usable proof.
|
|
52
|
-
*/
|
|
53
|
-
export declare class RecentWorkspaceSyncProofCache {
|
|
54
|
-
private readonly maxAgeMs;
|
|
55
|
-
private generation;
|
|
56
|
-
private proof;
|
|
57
|
-
constructor(maxAgeMs?: number);
|
|
58
|
-
beginObservation(): number;
|
|
59
|
-
invalidate(): void;
|
|
60
|
-
recordStable(input: {
|
|
61
|
-
observationGeneration: number;
|
|
62
|
-
canonicalHead: string | null;
|
|
63
|
-
scopeKey: string;
|
|
64
|
-
observedAtMs: number;
|
|
65
|
-
workerBusy: boolean;
|
|
66
|
-
}): boolean;
|
|
67
|
-
evaluate(input: WorkspaceSyncFastPathInput): WorkspaceSyncFastPathDecision;
|
|
68
|
-
}
|
|
69
|
-
export {};
|