audit-tools 0.30.6 → 0.30.7
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/remediate/dispatch/amendmentClaim.d.ts +7 -0
- package/dist/remediate/dispatch/amendmentClaim.d.ts.map +1 -1
- package/dist/remediate/dispatch/amendmentClaim.js +7 -0
- package/dist/remediate/dispatch/amendmentClaim.js.map +1 -1
- package/dist/remediate/dispatch/ownershipRegistry.d.ts +88 -2
- package/dist/remediate/dispatch/ownershipRegistry.d.ts.map +1 -1
- package/dist/remediate/dispatch/ownershipRegistry.js +224 -11
- package/dist/remediate/dispatch/ownershipRegistry.js.map +1 -1
- package/dist/remediate/dispatch/ownershipScheduler.d.ts +48 -0
- package/dist/remediate/dispatch/ownershipScheduler.d.ts.map +1 -0
- package/dist/remediate/dispatch/ownershipScheduler.js +100 -0
- package/dist/remediate/dispatch/ownershipScheduler.js.map +1 -0
- package/dist/remediate/steps/nextStep.d.ts +17 -2
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +44 -22
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/shared/repair/brokeredDispatch.d.ts +1 -1
- package/package.json +3 -2
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* it submits the candidate paths to `routeAmendmentRequest`. Unowned paths are
|
|
6
6
|
* granted unilaterally; owned or contended paths are routed back through the
|
|
7
7
|
* seam detect+resolve protocol.
|
|
8
|
+
*
|
|
9
|
+
* Grant-time disjointness (INV-SOO-06 / CE-001): `registry.claimAmendment`
|
|
10
|
+
* additionally refuses a scope-widening grant onto a file another node holds as a
|
|
11
|
+
* live in-flight scheduling claim, so the in-flight owned-file union can never
|
|
12
|
+
* become non-disjoint via a post-admission amendment. Such a path is routed as a
|
|
13
|
+
* `contended` seam (queued until the holder's file frees), exactly like a
|
|
14
|
+
* sibling-amendment contention.
|
|
8
15
|
*/
|
|
9
16
|
import type { OwnershipRegistry } from "./ownershipRegistry.js";
|
|
10
17
|
/** Discriminated union for the outcome of a single amendment claim attempt. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"amendmentClaim.d.ts","sourceRoot":"","sources":["../../../src/remediate/dispatch/amendmentClaim.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"amendmentClaim.d.ts","sourceRoot":"","sources":["../../../src/remediate/dispatch/amendmentClaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EAAE,GACvB;IACD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;CACpE,CAgDA"}
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* it submits the candidate paths to `routeAmendmentRequest`. Unowned paths are
|
|
6
6
|
* granted unilaterally; owned or contended paths are routed back through the
|
|
7
7
|
* seam detect+resolve protocol.
|
|
8
|
+
*
|
|
9
|
+
* Grant-time disjointness (INV-SOO-06 / CE-001): `registry.claimAmendment`
|
|
10
|
+
* additionally refuses a scope-widening grant onto a file another node holds as a
|
|
11
|
+
* live in-flight scheduling claim, so the in-flight owned-file union can never
|
|
12
|
+
* become non-disjoint via a post-admission amendment. Such a path is routed as a
|
|
13
|
+
* `contended` seam (queued until the holder's file frees), exactly like a
|
|
14
|
+
* sibling-amendment contention.
|
|
8
15
|
*/
|
|
9
16
|
/**
|
|
10
17
|
* Route a worker's requested amendment paths into granted vs seam-routed.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"amendmentClaim.js","sourceRoot":"","sources":["../../../src/remediate/dispatch/amendmentClaim.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"amendmentClaim.js","sourceRoot":"","sources":["../../../src/remediate/dispatch/amendmentClaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAA2B,EAC3B,MAAc,EACd,cAAwB;IAKxB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,GAA0D,EAAE,CAAC;IAE9E,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAEtD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxB,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;YAChE,MAAM,KAAK,GAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;YAC9E,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EAAE,MAAM;gBACb,KAAK,EAAE,uBAAuB;gBAC9B,OAAO,EAAE,MAAM;gBACf,IAAI;gBACJ,OAAO,EAAE,OAAO;gBAChB,aAAa;gBACb,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aAC7B,CAAC,GAAG,IAAI,CACV,CAAC;YACF,SAAS;QACX,CAAC;QAED,YAAY;QACZ,MAAM,eAAe,GAAG,QAAQ,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;QACtE,MAAM,KAAK,GAAyB,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QACpF,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,uBAAuB;YAC9B,OAAO,EAAE,MAAM;YACf,IAAI;YACJ,OAAO,EAAE,WAAW;YACpB,eAAe;YACf,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAC7B,CAAC,GAAG,IAAI,CACV,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -10,6 +10,28 @@
|
|
|
10
10
|
* Stale in-flight claims (node ID not in the current implementation DAG) are
|
|
11
11
|
* purged on load.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* The single-sourced path canonicalization for ownership identity (INV-SOO-09).
|
|
15
|
+
*
|
|
16
|
+
* Resolves a path to a stable physical-file key: absolute (against `root` when
|
|
17
|
+
* supplied, else cwd), `..`/`.`-collapsed (via `resolve`), separators normalized
|
|
18
|
+
* to `/`, and case-folded on a case-insensitive filesystem. All spellings of one
|
|
19
|
+
* file therefore collide, closing the rel/abs/case/`..` mismatch (CE-004).
|
|
20
|
+
*
|
|
21
|
+
* Symlink identity is a RECORDED RESIDUAL, not silently treated as disjoint
|
|
22
|
+
* (INV-SOO-09 / fail-3): when `resolveSymlinks` is set and the path exists, the
|
|
23
|
+
* realpath is folded in so `link.ts → x.ts` collides; when realpath is
|
|
24
|
+
* unavailable (path absent, or FS symlink resolution out of scope) the lexical
|
|
25
|
+
* canonical key is used and the unresolved case degrades to the downstream merge
|
|
26
|
+
* guard rather than being asserted disjoint here.
|
|
27
|
+
*
|
|
28
|
+
* This is the ONLY normalization scheme for ownership identity — callers must
|
|
29
|
+
* not introduce a second one (failure-mode: path-identity mismatch).
|
|
30
|
+
*/
|
|
31
|
+
export declare function canonicalizeFilePath(path: string, opts?: {
|
|
32
|
+
root?: string;
|
|
33
|
+
resolveSymlinks?: boolean;
|
|
34
|
+
}): string;
|
|
13
35
|
export interface OwnershipRegistryJson {
|
|
14
36
|
/** nodeId → set of paths from that node's original contract scope */
|
|
15
37
|
contractScopes: Record<string, string[]>;
|
|
@@ -36,10 +58,22 @@ export declare class OwnershipRegistry {
|
|
|
36
58
|
* claimed this path via an amendment.
|
|
37
59
|
*/
|
|
38
60
|
private amendmentClaims;
|
|
61
|
+
/**
|
|
62
|
+
* canonical path → nodeId: scheduling-time in-flight WRITER claim. Lifted from
|
|
63
|
+
* the merge-time amendment signal into scheduling-time admission (INV-SOO):
|
|
64
|
+
* the rolling scheduler holds at most one in-flight writer per canonical path,
|
|
65
|
+
* so same-file nodes serialize while different-file nodes parallelize. Keyed by
|
|
66
|
+
* `canonicalizeFilePath` so all spellings of one file collide (INV-SOO-09).
|
|
67
|
+
*/
|
|
68
|
+
private inFlightClaims;
|
|
39
69
|
/** Optional path for checkpoint persistence. When provided, every mutation
|
|
40
70
|
* atomically writes the registry state to this path. */
|
|
41
71
|
private checkpointPath;
|
|
42
|
-
|
|
72
|
+
/** Repo root for canonicalizing relative scope/claim paths to physical identity. */
|
|
73
|
+
private root;
|
|
74
|
+
constructor(checkpointPath?: string, root?: string);
|
|
75
|
+
/** Canonicalize a path to physical-file identity under this registry's root. */
|
|
76
|
+
private canon;
|
|
43
77
|
/**
|
|
44
78
|
* Initialize the registry from the implementation DAG nodes. Each node's
|
|
45
79
|
* `affected_files` (or `write_paths`) constitutes its contract scope.
|
|
@@ -60,6 +94,10 @@ export declare class OwnershipRegistry {
|
|
|
60
94
|
* - `'contended'` — path has been claimed by a live parallel sibling; routes to seam protocol.
|
|
61
95
|
*/
|
|
62
96
|
claimAmendment(nodeId: string, path: string): "granted" | "owned" | "contended";
|
|
97
|
+
/** The node holding an amendment claim whose canonical key matches `canonicalKey`. */
|
|
98
|
+
private _amendmentClaimantByCanonical;
|
|
99
|
+
/** Whether a contract-scope set contains a path with the given canonical key. */
|
|
100
|
+
private _scopeHasCanonical;
|
|
63
101
|
/**
|
|
64
102
|
* Release all amendment claims for `nodeId`. Call when a node finishes
|
|
65
103
|
* (merge or triage). Makes the previously claimed files available again.
|
|
@@ -78,9 +116,57 @@ export declare class OwnershipRegistry {
|
|
|
78
116
|
contractOwner(path: string): string | undefined;
|
|
79
117
|
/**
|
|
80
118
|
* Return the node ID that has an in-flight amendment claim for this path, or
|
|
81
|
-
* undefined if uncontended.
|
|
119
|
+
* undefined if uncontended. Canonical identity (INV-SOO-09).
|
|
82
120
|
*/
|
|
83
121
|
amendmentClaimant(path: string): string | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Canonical write-scope of a node for scheduling: its contract scope ∪ live
|
|
124
|
+
* amendment claims, each canonicalized to physical-file identity (INV-SOO-09).
|
|
125
|
+
*/
|
|
126
|
+
canonicalScope(nodeId: string): Set<string>;
|
|
127
|
+
/**
|
|
128
|
+
* The union of canonical file paths owned by currently in-flight nodes (their
|
|
129
|
+
* scheduling claims). The scheduling-time complement of the merge-time owner
|
|
130
|
+
* signal — a path in this set has a live writer, so a same-file node must wait
|
|
131
|
+
* (INV-SOO-01).
|
|
132
|
+
*/
|
|
133
|
+
filesClaimedByInFlight(): Set<string>;
|
|
134
|
+
/** The node holding the in-flight scheduling claim on `path`, or undefined. */
|
|
135
|
+
inFlightOwner(path: string): string | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Whether `nodeId`'s declared write-scope is file-ownership-DISJOINT from the
|
|
138
|
+
* current in-flight set — i.e. it shares no canonical path with any OTHER
|
|
139
|
+
* in-flight node, so it can be admitted without two in-flight writers per file
|
|
140
|
+
* (INV-SOO-01).
|
|
141
|
+
*
|
|
142
|
+
* Conservative empty-scope gating (INV-SOO-01 / CE-008): a node whose declared
|
|
143
|
+
* scope canonicalizes to the EMPTY set (unresolved/undeclared) is treated as
|
|
144
|
+
* NON-disjoint — it is NOT admitted as vacuously-disjoint, because an
|
|
145
|
+
* under-declared writer could collide with anything. Such a node admits only
|
|
146
|
+
* when no other node is in flight (`requireSoloWhenEmpty`).
|
|
147
|
+
*/
|
|
148
|
+
isFileOwnershipDisjoint(nodeId: string, declaredScope: Iterable<string>): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Record `nodeId`'s scheduling-time in-flight WRITER claim over `paths`
|
|
151
|
+
* (declared scope, canonicalized). Idempotent for a node re-claiming its own
|
|
152
|
+
* paths (a rate-limited re-queue). Call BEFORE dispatching the node.
|
|
153
|
+
*/
|
|
154
|
+
claimInFlight(nodeId: string, paths: Iterable<string>): void;
|
|
155
|
+
/**
|
|
156
|
+
* Release `nodeId`'s scheduling-time in-flight claims (and any amendment claims
|
|
157
|
+
* it holds). Call on a RELEASING disposition (INV-SOO-10):
|
|
158
|
+
* blocked-final / abandoned / merged / failed-no-retry / no-op-satisfied. A
|
|
159
|
+
* blocked-PENDING-triage node is still live and must NOT call this — it retains.
|
|
160
|
+
*/
|
|
161
|
+
releaseInFlight(nodeId: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* Atomic triage-retry claim hand-off A→A' (INV-SOO-07): transfer all of the
|
|
164
|
+
* failed node's in-flight + amendment claims to its successor in one step, so
|
|
165
|
+
* there is no observable window in which both hold the file, nor one in which a
|
|
166
|
+
* foreign same-file node is admitted between release and re-claim. A no-op when
|
|
167
|
+
* `fromNodeId === toNodeId`.
|
|
168
|
+
*/
|
|
169
|
+
handoffInFlight(fromNodeId: string, toNodeId: string): void;
|
|
84
170
|
/** Serialize to a plain JSON-safe object. */
|
|
85
171
|
serialize(): OwnershipRegistryJson;
|
|
86
172
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ownershipRegistry.d.ts","sourceRoot":"","sources":["../../../src/remediate/dispatch/ownershipRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"ownershipRegistry.d.ts","sourceRoot":"","sources":["../../../src/remediate/dispatch/ownershipRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,51 @@
|
|
|
10
10
|
* Stale in-flight claims (node ID not in the current implementation DAG) are
|
|
11
11
|
* purged on load.
|
|
12
12
|
*/
|
|
13
|
-
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
14
|
-
import { dirname } from "node:path";
|
|
13
|
+
import { writeFileSync, readFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
14
|
+
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
15
|
+
/**
|
|
16
|
+
* Whether the host filesystem is case-insensitive for path identity. win32 and
|
|
17
|
+
* darwin default to case-insensitive volumes; linux is case-sensitive. Used so
|
|
18
|
+
* `src/A.ts` and `src/a.ts` collide on a Windows/macOS volume (one physical
|
|
19
|
+
* file) but stay distinct on Linux — INV-SOO-09 canonical physical-file identity.
|
|
20
|
+
*/
|
|
21
|
+
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
22
|
+
/**
|
|
23
|
+
* The single-sourced path canonicalization for ownership identity (INV-SOO-09).
|
|
24
|
+
*
|
|
25
|
+
* Resolves a path to a stable physical-file key: absolute (against `root` when
|
|
26
|
+
* supplied, else cwd), `..`/`.`-collapsed (via `resolve`), separators normalized
|
|
27
|
+
* to `/`, and case-folded on a case-insensitive filesystem. All spellings of one
|
|
28
|
+
* file therefore collide, closing the rel/abs/case/`..` mismatch (CE-004).
|
|
29
|
+
*
|
|
30
|
+
* Symlink identity is a RECORDED RESIDUAL, not silently treated as disjoint
|
|
31
|
+
* (INV-SOO-09 / fail-3): when `resolveSymlinks` is set and the path exists, the
|
|
32
|
+
* realpath is folded in so `link.ts → x.ts` collides; when realpath is
|
|
33
|
+
* unavailable (path absent, or FS symlink resolution out of scope) the lexical
|
|
34
|
+
* canonical key is used and the unresolved case degrades to the downstream merge
|
|
35
|
+
* guard rather than being asserted disjoint here.
|
|
36
|
+
*
|
|
37
|
+
* This is the ONLY normalization scheme for ownership identity — callers must
|
|
38
|
+
* not introduce a second one (failure-mode: path-identity mismatch).
|
|
39
|
+
*/
|
|
40
|
+
export function canonicalizeFilePath(path, opts = {}) {
|
|
41
|
+
const { root, resolveSymlinks = false } = opts;
|
|
42
|
+
let abs = isAbsolute(path)
|
|
43
|
+
? resolve(path)
|
|
44
|
+
: resolve(root ?? process.cwd(), path);
|
|
45
|
+
if (resolveSymlinks) {
|
|
46
|
+
try {
|
|
47
|
+
abs = realpathSync(abs);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Symlink/realpath unavailable (path absent or FS resolution out of scope):
|
|
51
|
+
// keep the lexical key — the unresolved symlink case is a recorded residual
|
|
52
|
+
// handled by the downstream merge guard, never asserted disjoint here.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const slashed = abs.split(sep).join("/");
|
|
56
|
+
return CASE_INSENSITIVE_FS ? slashed.toLowerCase() : slashed;
|
|
57
|
+
}
|
|
15
58
|
/**
|
|
16
59
|
* OwnershipRegistry tracks write-scope ownership per DAG node.
|
|
17
60
|
*
|
|
@@ -32,11 +75,26 @@ export class OwnershipRegistry {
|
|
|
32
75
|
* claimed this path via an amendment.
|
|
33
76
|
*/
|
|
34
77
|
amendmentClaims = new Map();
|
|
78
|
+
/**
|
|
79
|
+
* canonical path → nodeId: scheduling-time in-flight WRITER claim. Lifted from
|
|
80
|
+
* the merge-time amendment signal into scheduling-time admission (INV-SOO):
|
|
81
|
+
* the rolling scheduler holds at most one in-flight writer per canonical path,
|
|
82
|
+
* so same-file nodes serialize while different-file nodes parallelize. Keyed by
|
|
83
|
+
* `canonicalizeFilePath` so all spellings of one file collide (INV-SOO-09).
|
|
84
|
+
*/
|
|
85
|
+
inFlightClaims = new Map();
|
|
35
86
|
/** Optional path for checkpoint persistence. When provided, every mutation
|
|
36
87
|
* atomically writes the registry state to this path. */
|
|
37
88
|
checkpointPath;
|
|
38
|
-
|
|
89
|
+
/** Repo root for canonicalizing relative scope/claim paths to physical identity. */
|
|
90
|
+
root;
|
|
91
|
+
constructor(checkpointPath, root) {
|
|
39
92
|
this.checkpointPath = checkpointPath;
|
|
93
|
+
this.root = root;
|
|
94
|
+
}
|
|
95
|
+
/** Canonicalize a path to physical-file identity under this registry's root. */
|
|
96
|
+
canon(path) {
|
|
97
|
+
return canonicalizeFilePath(path, { root: this.root });
|
|
40
98
|
}
|
|
41
99
|
/**
|
|
42
100
|
* Initialize the registry from the implementation DAG nodes. Each node's
|
|
@@ -62,22 +120,48 @@ export class OwnershipRegistry {
|
|
|
62
120
|
* - `'contended'` — path has been claimed by a live parallel sibling; routes to seam protocol.
|
|
63
121
|
*/
|
|
64
122
|
claimAmendment(nodeId, path) {
|
|
65
|
-
|
|
123
|
+
const key = this.canon(path);
|
|
124
|
+
// Check if path is in any OTHER node's contract scope (canonical identity).
|
|
66
125
|
for (const [scopeNodeId, scopePaths] of this.contractScopes) {
|
|
67
|
-
if (scopeNodeId !== nodeId &&
|
|
126
|
+
if (scopeNodeId !== nodeId && this._scopeHasCanonical(scopePaths, key)) {
|
|
68
127
|
return "owned";
|
|
69
128
|
}
|
|
70
129
|
}
|
|
71
|
-
// Check if path has already been amendment-claimed by a live parallel sibling
|
|
72
|
-
|
|
130
|
+
// Check if path has already been amendment-claimed by a live parallel sibling
|
|
131
|
+
// (canonical identity, so a differently-spelled same file collides).
|
|
132
|
+
const existingClaimant = this._amendmentClaimantByCanonical(key);
|
|
73
133
|
if (existingClaimant !== undefined && existingClaimant !== nodeId) {
|
|
74
134
|
return "contended";
|
|
75
135
|
}
|
|
76
|
-
// Grant:
|
|
136
|
+
// Grant-time disjointness (INV-SOO-06 / CE-001): refuse a scope-widening grant
|
|
137
|
+
// onto a file another in-flight node is actively writing, so a post-admission
|
|
138
|
+
// grant can never make the in-flight owned-file union non-disjoint.
|
|
139
|
+
const inFlightOwner = this.inFlightClaims.get(key);
|
|
140
|
+
if (inFlightOwner !== undefined && inFlightOwner !== nodeId) {
|
|
141
|
+
return "contended";
|
|
142
|
+
}
|
|
143
|
+
// Grant: record the claim under the ORIGINAL spelling (merge attribution reads
|
|
144
|
+
// the raw path); canonical identity is applied on comparison, not on storage.
|
|
77
145
|
this.amendmentClaims.set(path, nodeId);
|
|
78
146
|
this._persist();
|
|
79
147
|
return "granted";
|
|
80
148
|
}
|
|
149
|
+
/** The node holding an amendment claim whose canonical key matches `canonicalKey`. */
|
|
150
|
+
_amendmentClaimantByCanonical(canonicalKey) {
|
|
151
|
+
for (const [p, claimant] of this.amendmentClaims) {
|
|
152
|
+
if (this.canon(p) === canonicalKey)
|
|
153
|
+
return claimant;
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
/** Whether a contract-scope set contains a path with the given canonical key. */
|
|
158
|
+
_scopeHasCanonical(scopePaths, canonicalKey) {
|
|
159
|
+
for (const p of scopePaths) {
|
|
160
|
+
if (this.canon(p) === canonicalKey)
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
81
165
|
/**
|
|
82
166
|
* Release all amendment claims for `nodeId`. Call when a node finishes
|
|
83
167
|
* (merge or triage). Makes the previously claimed files available again.
|
|
@@ -114,18 +198,147 @@ export class OwnershipRegistry {
|
|
|
114
198
|
* the path is unowned.
|
|
115
199
|
*/
|
|
116
200
|
contractOwner(path) {
|
|
201
|
+
const key = this.canon(path);
|
|
117
202
|
for (const [nodeId, scopePaths] of this.contractScopes) {
|
|
118
|
-
if (
|
|
203
|
+
if (this._scopeHasCanonical(scopePaths, key))
|
|
119
204
|
return nodeId;
|
|
120
205
|
}
|
|
121
206
|
return undefined;
|
|
122
207
|
}
|
|
123
208
|
/**
|
|
124
209
|
* Return the node ID that has an in-flight amendment claim for this path, or
|
|
125
|
-
* undefined if uncontended.
|
|
210
|
+
* undefined if uncontended. Canonical identity (INV-SOO-09).
|
|
126
211
|
*/
|
|
127
212
|
amendmentClaimant(path) {
|
|
128
|
-
|
|
213
|
+
// Exact-spelling hit first (cheap), else canonical match.
|
|
214
|
+
return (this.amendmentClaims.get(path) ??
|
|
215
|
+
this._amendmentClaimantByCanonical(this.canon(path)));
|
|
216
|
+
}
|
|
217
|
+
// ── Scheduling-time file-ownership queries (INV-SOO) ───────────────────────
|
|
218
|
+
/**
|
|
219
|
+
* Canonical write-scope of a node for scheduling: its contract scope ∪ live
|
|
220
|
+
* amendment claims, each canonicalized to physical-file identity (INV-SOO-09).
|
|
221
|
+
*/
|
|
222
|
+
canonicalScope(nodeId) {
|
|
223
|
+
const out = new Set();
|
|
224
|
+
for (const p of this.contractScopes.get(nodeId) ?? [])
|
|
225
|
+
out.add(this.canon(p));
|
|
226
|
+
for (const [key, claimant] of this.amendmentClaims) {
|
|
227
|
+
if (claimant === nodeId)
|
|
228
|
+
out.add(key);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The union of canonical file paths owned by currently in-flight nodes (their
|
|
234
|
+
* scheduling claims). The scheduling-time complement of the merge-time owner
|
|
235
|
+
* signal — a path in this set has a live writer, so a same-file node must wait
|
|
236
|
+
* (INV-SOO-01).
|
|
237
|
+
*/
|
|
238
|
+
filesClaimedByInFlight() {
|
|
239
|
+
return new Set(this.inFlightClaims.keys());
|
|
240
|
+
}
|
|
241
|
+
/** The node holding the in-flight scheduling claim on `path`, or undefined. */
|
|
242
|
+
inFlightOwner(path) {
|
|
243
|
+
return this.inFlightClaims.get(this.canon(path));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Whether `nodeId`'s declared write-scope is file-ownership-DISJOINT from the
|
|
247
|
+
* current in-flight set — i.e. it shares no canonical path with any OTHER
|
|
248
|
+
* in-flight node, so it can be admitted without two in-flight writers per file
|
|
249
|
+
* (INV-SOO-01).
|
|
250
|
+
*
|
|
251
|
+
* Conservative empty-scope gating (INV-SOO-01 / CE-008): a node whose declared
|
|
252
|
+
* scope canonicalizes to the EMPTY set (unresolved/undeclared) is treated as
|
|
253
|
+
* NON-disjoint — it is NOT admitted as vacuously-disjoint, because an
|
|
254
|
+
* under-declared writer could collide with anything. Such a node admits only
|
|
255
|
+
* when no other node is in flight (`requireSoloWhenEmpty`).
|
|
256
|
+
*/
|
|
257
|
+
isFileOwnershipDisjoint(nodeId, declaredScope) {
|
|
258
|
+
const scope = new Set();
|
|
259
|
+
for (const p of declaredScope)
|
|
260
|
+
scope.add(this.canon(p));
|
|
261
|
+
if (scope.size === 0) {
|
|
262
|
+
// Empty/unresolved scope: disjoint only if nothing else is in flight.
|
|
263
|
+
for (const owner of this.inFlightClaims.values()) {
|
|
264
|
+
if (owner !== nodeId)
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
for (const key of scope) {
|
|
270
|
+
const owner = this.inFlightClaims.get(key);
|
|
271
|
+
if (owner !== undefined && owner !== nodeId)
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Record `nodeId`'s scheduling-time in-flight WRITER claim over `paths`
|
|
278
|
+
* (declared scope, canonicalized). Idempotent for a node re-claiming its own
|
|
279
|
+
* paths (a rate-limited re-queue). Call BEFORE dispatching the node.
|
|
280
|
+
*/
|
|
281
|
+
claimInFlight(nodeId, paths) {
|
|
282
|
+
let changed = false;
|
|
283
|
+
for (const p of paths) {
|
|
284
|
+
const key = this.canon(p);
|
|
285
|
+
const existing = this.inFlightClaims.get(key);
|
|
286
|
+
if (existing === nodeId)
|
|
287
|
+
continue;
|
|
288
|
+
// Caller guarantees disjointness via isFileOwnershipDisjoint; a foreign
|
|
289
|
+
// owner here is a precondition violation, surfaced rather than overwritten.
|
|
290
|
+
if (existing !== undefined && existing !== nodeId) {
|
|
291
|
+
throw new Error(`OwnershipRegistry.claimInFlight: ${key} already in-flight by ${existing}, ` +
|
|
292
|
+
`cannot claim for ${nodeId} (file-ownership-disjoint precondition violated).`);
|
|
293
|
+
}
|
|
294
|
+
this.inFlightClaims.set(key, nodeId);
|
|
295
|
+
changed = true;
|
|
296
|
+
}
|
|
297
|
+
if (changed)
|
|
298
|
+
this._persist();
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Release `nodeId`'s scheduling-time in-flight claims (and any amendment claims
|
|
302
|
+
* it holds). Call on a RELEASING disposition (INV-SOO-10):
|
|
303
|
+
* blocked-final / abandoned / merged / failed-no-retry / no-op-satisfied. A
|
|
304
|
+
* blocked-PENDING-triage node is still live and must NOT call this — it retains.
|
|
305
|
+
*/
|
|
306
|
+
releaseInFlight(nodeId) {
|
|
307
|
+
let changed = false;
|
|
308
|
+
for (const [key, owner] of this.inFlightClaims) {
|
|
309
|
+
if (owner === nodeId) {
|
|
310
|
+
this.inFlightClaims.delete(key);
|
|
311
|
+
changed = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (changed)
|
|
315
|
+
this._persist();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Atomic triage-retry claim hand-off A→A' (INV-SOO-07): transfer all of the
|
|
319
|
+
* failed node's in-flight + amendment claims to its successor in one step, so
|
|
320
|
+
* there is no observable window in which both hold the file, nor one in which a
|
|
321
|
+
* foreign same-file node is admitted between release and re-claim. A no-op when
|
|
322
|
+
* `fromNodeId === toNodeId`.
|
|
323
|
+
*/
|
|
324
|
+
handoffInFlight(fromNodeId, toNodeId) {
|
|
325
|
+
if (fromNodeId === toNodeId)
|
|
326
|
+
return;
|
|
327
|
+
let changed = false;
|
|
328
|
+
for (const [key, owner] of this.inFlightClaims) {
|
|
329
|
+
if (owner === fromNodeId) {
|
|
330
|
+
this.inFlightClaims.set(key, toNodeId);
|
|
331
|
+
changed = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const [key, owner] of this.amendmentClaims) {
|
|
335
|
+
if (owner === fromNodeId) {
|
|
336
|
+
this.amendmentClaims.set(key, toNodeId);
|
|
337
|
+
changed = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (changed)
|
|
341
|
+
this._persist();
|
|
129
342
|
}
|
|
130
343
|
/** Serialize to a plain JSON-safe object. */
|
|
131
344
|
serialize() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ownershipRegistry.js","sourceRoot":"","sources":["../../../src/remediate/dispatch/ownershipRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ownershipRegistry.js","sourceRoot":"","sources":["../../../src/remediate/dispatch/ownershipRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-ownership-disjoint admission scheduling (INV-SOO-01..10).
|
|
3
|
+
*
|
|
4
|
+
* The rolling engine dispatches an eligible dependency LEVEL (deps verified-
|
|
5
|
+
* complete, INV-RS-01). Within a level the previous scheduler ordered nodes only
|
|
6
|
+
* by `block_id.localeCompare`, which let two same-file nodes run concurrently and
|
|
7
|
+
* collide at merge (the lost-update-guard-lands-1-rejects-N-1 starved-tail bug).
|
|
8
|
+
*
|
|
9
|
+
* This module replaces that numeric ordering with file-ownership-DISJOINT
|
|
10
|
+
* admission: a level is admitted in SUB-WAVES, each a maximal pairwise-file-
|
|
11
|
+
* disjoint subset of the still-pending nodes, chosen by a deterministic
|
|
12
|
+
* block_id tie-break AFTER the disjointness filter (INV-SOO-08). Same-file nodes
|
|
13
|
+
* therefore land in successive sub-waves (serialize, INV-SOO-01/02) while
|
|
14
|
+
* different-file nodes share a sub-wave (parallelize up to the quota cap the
|
|
15
|
+
* dispatcher itself enforces, INV-SOO-03/05). Path identity is the single
|
|
16
|
+
* canonical key from `canonicalizeFilePath` (INV-SOO-09); an empty/unresolved
|
|
17
|
+
* scope is conservatively NON-disjoint so it never batches with a peer
|
|
18
|
+
* (INV-SOO-01 / CE-008).
|
|
19
|
+
*
|
|
20
|
+
* Pure and deterministic — no I/O, no Set/Map iteration-order leak — so the
|
|
21
|
+
* admission order is reproducible and unit-testable against a precomputed level.
|
|
22
|
+
*/
|
|
23
|
+
/** A node as the ownership scheduler sees it: an id + its declared write-scope. */
|
|
24
|
+
export interface OwnershipSchedulerNode {
|
|
25
|
+
block_id: string;
|
|
26
|
+
/** Declared write-scope paths (repo-relative or absolute); empty ⇒ unresolved. */
|
|
27
|
+
write_paths: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Canonicalize a node's declared write-scope to physical-file identity keys.
|
|
31
|
+
* Empty input ⇒ empty set (handled conservatively by the disjointness gate).
|
|
32
|
+
*/
|
|
33
|
+
export declare function canonicalScopeKeys(node: OwnershipSchedulerNode, root?: string): Set<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Partition a single eligible LEVEL into ordered file-ownership-disjoint
|
|
36
|
+
* SUB-WAVES. Each sub-wave is a maximal set of nodes whose canonical write-scopes
|
|
37
|
+
* are pairwise disjoint AND disjoint from a node with an empty scope only when it
|
|
38
|
+
* is alone (conservative empty-scope gating). Nodes are considered in ascending
|
|
39
|
+
* `block_id` order (the deterministic tie-break, INV-SOO-08), so the admitted
|
|
40
|
+
* subset of each sub-wave is identical across two runs over identical state.
|
|
41
|
+
*
|
|
42
|
+
* A node with an EMPTY canonical scope (unresolved/undeclared) is treated as
|
|
43
|
+
* conservatively NON-disjoint: it only enters a sub-wave by itself (it blocks and
|
|
44
|
+
* is blocked-by every peer), so it never batches with another writer
|
|
45
|
+
* (INV-SOO-01 / CE-008).
|
|
46
|
+
*/
|
|
47
|
+
export declare function ownershipSubWaves(level: OwnershipSchedulerNode[], root?: string): OwnershipSchedulerNode[][];
|
|
48
|
+
//# sourceMappingURL=ownershipScheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownershipScheduler.d.ts","sourceRoot":"","sources":["../../../src/remediate/dispatch/ownershipScheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,sBAAsB,EAC5B,IAAI,CAAC,EAAE,MAAM,GACZ,GAAG,CAAC,MAAM,CAAC,CAIb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,EAAE,EAC/B,IAAI,CAAC,EAAE,MAAM,GACZ,sBAAsB,EAAE,EAAE,CAwD5B"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-ownership-disjoint admission scheduling (INV-SOO-01..10).
|
|
3
|
+
*
|
|
4
|
+
* The rolling engine dispatches an eligible dependency LEVEL (deps verified-
|
|
5
|
+
* complete, INV-RS-01). Within a level the previous scheduler ordered nodes only
|
|
6
|
+
* by `block_id.localeCompare`, which let two same-file nodes run concurrently and
|
|
7
|
+
* collide at merge (the lost-update-guard-lands-1-rejects-N-1 starved-tail bug).
|
|
8
|
+
*
|
|
9
|
+
* This module replaces that numeric ordering with file-ownership-DISJOINT
|
|
10
|
+
* admission: a level is admitted in SUB-WAVES, each a maximal pairwise-file-
|
|
11
|
+
* disjoint subset of the still-pending nodes, chosen by a deterministic
|
|
12
|
+
* block_id tie-break AFTER the disjointness filter (INV-SOO-08). Same-file nodes
|
|
13
|
+
* therefore land in successive sub-waves (serialize, INV-SOO-01/02) while
|
|
14
|
+
* different-file nodes share a sub-wave (parallelize up to the quota cap the
|
|
15
|
+
* dispatcher itself enforces, INV-SOO-03/05). Path identity is the single
|
|
16
|
+
* canonical key from `canonicalizeFilePath` (INV-SOO-09); an empty/unresolved
|
|
17
|
+
* scope is conservatively NON-disjoint so it never batches with a peer
|
|
18
|
+
* (INV-SOO-01 / CE-008).
|
|
19
|
+
*
|
|
20
|
+
* Pure and deterministic — no I/O, no Set/Map iteration-order leak — so the
|
|
21
|
+
* admission order is reproducible and unit-testable against a precomputed level.
|
|
22
|
+
*/
|
|
23
|
+
import { canonicalizeFilePath } from "./ownershipRegistry.js";
|
|
24
|
+
/**
|
|
25
|
+
* Canonicalize a node's declared write-scope to physical-file identity keys.
|
|
26
|
+
* Empty input ⇒ empty set (handled conservatively by the disjointness gate).
|
|
27
|
+
*/
|
|
28
|
+
export function canonicalScopeKeys(node, root) {
|
|
29
|
+
const out = new Set();
|
|
30
|
+
for (const p of node.write_paths)
|
|
31
|
+
out.add(canonicalizeFilePath(p, { root }));
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Partition a single eligible LEVEL into ordered file-ownership-disjoint
|
|
36
|
+
* SUB-WAVES. Each sub-wave is a maximal set of nodes whose canonical write-scopes
|
|
37
|
+
* are pairwise disjoint AND disjoint from a node with an empty scope only when it
|
|
38
|
+
* is alone (conservative empty-scope gating). Nodes are considered in ascending
|
|
39
|
+
* `block_id` order (the deterministic tie-break, INV-SOO-08), so the admitted
|
|
40
|
+
* subset of each sub-wave is identical across two runs over identical state.
|
|
41
|
+
*
|
|
42
|
+
* A node with an EMPTY canonical scope (unresolved/undeclared) is treated as
|
|
43
|
+
* conservatively NON-disjoint: it only enters a sub-wave by itself (it blocks and
|
|
44
|
+
* is blocked-by every peer), so it never batches with another writer
|
|
45
|
+
* (INV-SOO-01 / CE-008).
|
|
46
|
+
*/
|
|
47
|
+
export function ownershipSubWaves(level, root) {
|
|
48
|
+
const ordered = [...level].sort((a, b) => a.block_id.localeCompare(b.block_id));
|
|
49
|
+
const scopeOf = new Map();
|
|
50
|
+
for (const n of ordered)
|
|
51
|
+
scopeOf.set(n.block_id, canonicalScopeKeys(n, root));
|
|
52
|
+
const remaining = [...ordered];
|
|
53
|
+
const subWaves = [];
|
|
54
|
+
while (remaining.length > 0) {
|
|
55
|
+
const wave = [];
|
|
56
|
+
const claimed = new Set();
|
|
57
|
+
let waveHasEmptyScopeNode = false;
|
|
58
|
+
const leftover = [];
|
|
59
|
+
for (const node of remaining) {
|
|
60
|
+
const scope = scopeOf.get(node.block_id);
|
|
61
|
+
const isEmpty = scope.size === 0;
|
|
62
|
+
// Empty-scope node admits only into an otherwise-empty sub-wave (solo).
|
|
63
|
+
if (isEmpty) {
|
|
64
|
+
if (wave.length === 0) {
|
|
65
|
+
wave.push(node);
|
|
66
|
+
waveHasEmptyScopeNode = true;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
leftover.push(node);
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// A real-scope node cannot share a wave with an empty-scope node, nor with
|
|
74
|
+
// any node it shares a canonical path with.
|
|
75
|
+
if (waveHasEmptyScopeNode) {
|
|
76
|
+
leftover.push(node);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let overlaps = false;
|
|
80
|
+
for (const key of scope) {
|
|
81
|
+
if (claimed.has(key)) {
|
|
82
|
+
overlaps = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (overlaps) {
|
|
87
|
+
leftover.push(node);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const key of scope)
|
|
91
|
+
claimed.add(key);
|
|
92
|
+
wave.push(node);
|
|
93
|
+
}
|
|
94
|
+
subWaves.push(wave);
|
|
95
|
+
remaining.length = 0;
|
|
96
|
+
remaining.push(...leftover);
|
|
97
|
+
}
|
|
98
|
+
return subWaves;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=ownershipScheduler.js.map
|