@forgeax/engine-ddc 0.1.4 → 0.1.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/README.md +9 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +213 -93
- package/dist/index.mjs.map +1 -1
- package/dist/lifecycle.d.ts +38 -3
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/publication.d.ts.map +1 -1
- package/dist/session.d.ts +4 -3
- package/dist/session.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/generation-session.integration.test.ts +132 -4
- package/src/__tests__/multiprocess-lifecycle.integration.test.ts +21 -1
- package/src/index.ts +4 -0
- package/src/lifecycle.ts +297 -92
- package/src/publication.ts +12 -10
- package/src/session.ts +40 -13
package/dist/lifecycle.d.ts
CHANGED
|
@@ -26,6 +26,32 @@ export interface DdcCommitResult {
|
|
|
26
26
|
readonly result: 'current' | 'stale' | 'lease-lost' | 'invalid';
|
|
27
27
|
readonly key: string;
|
|
28
28
|
readonly revision?: number;
|
|
29
|
+
/** Internal CAS evidence retained for a later generation rollback. */
|
|
30
|
+
readonly restoreFence?: DdcRestoreFence;
|
|
31
|
+
}
|
|
32
|
+
export interface DdcRestoreFence {
|
|
33
|
+
readonly attempt: string;
|
|
34
|
+
readonly generation: number;
|
|
35
|
+
readonly revision: number;
|
|
36
|
+
readonly desiredKey: string;
|
|
37
|
+
readonly outcome: 'current' | 'invalid' | 'failed';
|
|
38
|
+
readonly key?: string;
|
|
39
|
+
readonly currentKey?: string;
|
|
40
|
+
readonly lastKnownGoodKey?: string;
|
|
41
|
+
readonly failure?: {
|
|
42
|
+
readonly code: string;
|
|
43
|
+
readonly detail: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface DdcRestoreResult {
|
|
47
|
+
readonly result: 'restored' | 'not-owner';
|
|
48
|
+
readonly revision: number;
|
|
49
|
+
readonly generation?: number;
|
|
50
|
+
}
|
|
51
|
+
export type DdcRollbackSnapshot = Omit<DdcHead, 'activeLease'>;
|
|
52
|
+
export interface DdcBeginResult {
|
|
53
|
+
readonly lease: DdcLease;
|
|
54
|
+
readonly previousHead: DdcRollbackSnapshot;
|
|
29
55
|
}
|
|
30
56
|
/** Cross-process mkdir lock; a lock owned by a dead PID is reclaimable. */
|
|
31
57
|
export declare function withDdcLock<T>(root: string, name: string, operation: () => Promise<T>): Promise<T>;
|
|
@@ -38,19 +64,28 @@ export declare class DdcLifecycle {
|
|
|
38
64
|
readonly leaseTtlMs?: number;
|
|
39
65
|
});
|
|
40
66
|
inspect(guid: string, desiredKey: string): Promise<DdcHead>;
|
|
67
|
+
/** Begin a lease and capture the accepted rollback snapshot under one head lock. */
|
|
68
|
+
beginWithSnapshot(guid: string, desiredKey: string): Promise<DdcBeginResult>;
|
|
41
69
|
begin(guid: string, desiredKey: string): Promise<DdcLease>;
|
|
42
70
|
commit(lease: DdcLease, validatedKey: string): Promise<DdcCommitResult>;
|
|
43
71
|
fail(lease: DdcLease, failure: {
|
|
44
72
|
readonly code: string;
|
|
45
73
|
readonly detail: string;
|
|
46
|
-
}): Promise<
|
|
74
|
+
}): Promise<DdcRestoreFence | undefined>;
|
|
47
75
|
heartbeat(lease: DdcLease): Promise<DdcLease>;
|
|
48
76
|
close(lease: DdcLease): Promise<void>;
|
|
49
77
|
discard(lease: DdcLease): Promise<void>;
|
|
50
|
-
/**
|
|
51
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Restore accepted content only when this lease still owns the mutable head.
|
|
80
|
+
* A newer active or terminal mutation makes rollback a deliberate no-op.
|
|
81
|
+
*/
|
|
82
|
+
restore(head: DdcHead | DdcRollbackSnapshot, lease?: DdcLease, fence?: DdcRestoreFence): Promise<DdcRestoreResult>;
|
|
83
|
+
restoreIfCurrent(snapshot: DdcRollbackSnapshot, lease: DdcLease, fence?: DdcRestoreFence): Promise<DdcRestoreResult>;
|
|
52
84
|
revoke(lease: DdcLease): Promise<void>;
|
|
53
85
|
recover(guid: string, desiredKey: string): Promise<DdcHead>;
|
|
86
|
+
private rollbackSnapshot;
|
|
87
|
+
private matchesRestoreFence;
|
|
88
|
+
private projectHead;
|
|
54
89
|
private allocateGeneration;
|
|
55
90
|
private read;
|
|
56
91
|
private write;
|
package/dist/lifecycle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publication.d.ts","sourceRoot":"","sources":["../src/publication.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,MAAM,EACP,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;CAC5C;AAED,MAAM,WAAW,8BAA+B,SAAQ,uBAAuB;IAC7E,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,aAAa,CAAC,EAAE,uBAAuB,CAAC;CAClD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACpD,QAAQ,CAAC,gBAAgB,EAAE,SAAS,gCAAgC,EAAE,CAAC;CACxE;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,iCAAiC,CAAC;IAC/D,KAAK,CACH,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,4BAA4B,GACtC,MAAM,CAAC,IAAI,EAAE,8BAA8B,CAAC,CAAC;IAChD,MAAM,CACJ,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,4BAA4B,EACvC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACtC,OAAO,CAAC,MAAM,CAAC,iCAAiC,EAAE,8BAA8B,CAAC,CAAC,CAAC;IACtF,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACvE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,iCAAiC,GAAG,IAAI,CAAC;CAChF;AAkBD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,sBAAsB,EAAE,GAAG,MAAM,CAUhG;AAED,+EAA+E;AAC/E,wBAAgB,mCAAmC,CAAC,KAAK,EAAE;IACzD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,GAAG,MAAM,CAUT;AAED,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,mCAAmC,GACzC,wBAAwB,CAmC1B;
|
|
1
|
+
{"version":3,"file":"publication.d.ts","sourceRoot":"","sources":["../src/publication.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,MAAM,EACP,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;CAC5C;AAED,MAAM,WAAW,8BAA+B,SAAQ,uBAAuB;IAC7E,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,aAAa,CAAC,EAAE,uBAAuB,CAAC;CAClD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACpD,QAAQ,CAAC,gBAAgB,EAAE,SAAS,gCAAgC,EAAE,CAAC;CACxE;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,iCAAiC,CAAC;IAC/D,KAAK,CACH,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,4BAA4B,GACtC,MAAM,CAAC,IAAI,EAAE,8BAA8B,CAAC,CAAC;IAChD,MAAM,CACJ,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,4BAA4B,EACvC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACtC,OAAO,CAAC,MAAM,CAAC,iCAAiC,EAAE,8BAA8B,CAAC,CAAC,CAAC;IACtF,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACvE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,iCAAiC,GAAG,IAAI,CAAC;CAChF;AAkBD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,sBAAsB,EAAE,GAAG,MAAM,CAUhG;AAED,+EAA+E;AAC/E,wBAAgB,mCAAmC,CAAC,KAAK,EAAE;IACzD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,GAAG,MAAM,CAUT;AAED,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,mCAAmC,GACzC,wBAAwB,CAmC1B;AAuPD,uEAAuE;AACvE,wBAAgB,8BAA8B,IAAI,wBAAwB,CAqDzE"}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DdcEntry, StagedDdcEntry } from './entry-store.js';
|
|
2
|
-
import type { DdcCommitResult, DdcHead, DdcLease } from './lifecycle.js';
|
|
2
|
+
import type { DdcCommitResult, DdcHead, DdcLease, DdcRestoreResult, DdcRollbackSnapshot } from './lifecycle.js';
|
|
3
3
|
export interface DdcSessionOptions {
|
|
4
4
|
readonly generation: number;
|
|
5
5
|
readonly leaseTtlMs?: number;
|
|
@@ -7,7 +7,7 @@ export interface DdcSessionOptions {
|
|
|
7
7
|
export interface DdcGenerationCandidate {
|
|
8
8
|
readonly generation: number;
|
|
9
9
|
readonly lease: DdcLease;
|
|
10
|
-
readonly previousHead:
|
|
10
|
+
readonly previousHead: DdcRollbackSnapshot;
|
|
11
11
|
}
|
|
12
12
|
export interface DdcGenerationEntryCandidate extends DdcGenerationCandidate {
|
|
13
13
|
readonly staged: StagedDdcEntry;
|
|
@@ -23,6 +23,7 @@ export declare class DdcGenerationSession {
|
|
|
23
23
|
private readonly lifecycle;
|
|
24
24
|
private readonly entries;
|
|
25
25
|
private readonly candidates;
|
|
26
|
+
private readonly restoreFences;
|
|
26
27
|
private readonly heartbeatTimers;
|
|
27
28
|
private readonly counters;
|
|
28
29
|
private accepting;
|
|
@@ -34,7 +35,7 @@ export declare class DdcGenerationSession {
|
|
|
34
35
|
commitEntry(candidate: DdcGenerationEntryCandidate, validatedKey: string): Promise<DdcCommitResult>;
|
|
35
36
|
discardCandidate(candidate: DdcGenerationCandidate): Promise<void>;
|
|
36
37
|
discardEntry(candidate: DdcGenerationEntryCandidate): Promise<void>;
|
|
37
|
-
restoreEntry(candidate: DdcGenerationEntryCandidate): Promise<
|
|
38
|
+
restoreEntry(candidate: DdcGenerationEntryCandidate): Promise<DdcRestoreResult>;
|
|
38
39
|
metrics(): DdcSessionMetrics;
|
|
39
40
|
close(): Promise<void>;
|
|
40
41
|
private assertOpen;
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,KAAK,EACV,eAAe,EACf,OAAO,EACP,QAAQ,EAER,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA4B,SAAQ,sBAAsB;IACzE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAmBD,qBAAa,oBAAoB;IAC/B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoD;IAC/E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0D;IACxF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAKvB;IACF,OAAO,CAAC,SAAS,CAAQ;gBAEN,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB;IAY9C,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAejF,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,2BAA2B,CAAC;IA0BjE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3D,eAAe,CAC1B,SAAS,EAAE,sBAAsB,EACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,CAAC;IAiBd,WAAW,CACtB,SAAS,EAAE,2BAA2B,EACtC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,CAAC;IAwBd,gBAAgB,CAAC,SAAS,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAalE,YAAY,CAAC,SAAS,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAInE,YAAY,CAAC,SAAS,EAAE,2BAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2BrF,OAAO,IAAI,iBAAiB;IAItB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;CAOtB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-ddc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -85,13 +85,13 @@
|
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@forgeax/engine-types": "0.1.
|
|
88
|
+
"@forgeax/engine-types": "0.1.7"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@types/node": "^20.14.0",
|
|
92
92
|
"tsup": "^8.3.5",
|
|
93
93
|
"typescript": "^5.8.3",
|
|
94
|
-
"vitest": "4.
|
|
94
|
+
"vitest": "4.0.18"
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|
|
97
97
|
"build": "tsup",
|
|
@@ -3,21 +3,24 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
5
|
import { type DdcEntry, DdcEntryStore, ddcOutputDigest } from '../entry-store.js';
|
|
6
|
+
import { DdcLifecycle } from '../lifecycle.js';
|
|
6
7
|
import { DdcGenerationSession } from '../session.js';
|
|
7
8
|
|
|
8
9
|
const GUID = '019e3969-1d48-7c3b-ac24-6d68f457065f';
|
|
9
10
|
const KEY = 'a'.repeat(64);
|
|
11
|
+
const KEY_B = 'b'.repeat(64);
|
|
12
|
+
const KEY_C = 'c'.repeat(64);
|
|
10
13
|
|
|
11
|
-
function entry(): DdcEntry {
|
|
14
|
+
function entry(key = KEY): DdcEntry {
|
|
12
15
|
const value = {
|
|
13
|
-
key
|
|
16
|
+
key,
|
|
14
17
|
guid: GUID,
|
|
15
|
-
payload: { kind: 'fixture' },
|
|
18
|
+
payload: { kind: 'fixture', key },
|
|
16
19
|
refs: [],
|
|
17
20
|
artifacts: {},
|
|
18
21
|
receipt: {
|
|
19
22
|
guid: GUID,
|
|
20
|
-
key
|
|
23
|
+
key,
|
|
21
24
|
producer: 'fixture',
|
|
22
25
|
inputFingerprint: 'sha256:source',
|
|
23
26
|
outputDigest: '',
|
|
@@ -71,4 +74,129 @@ describe('DDC generation session integration', () => {
|
|
|
71
74
|
await session.close();
|
|
72
75
|
}
|
|
73
76
|
});
|
|
77
|
+
|
|
78
|
+
it('does not let a stale rollback restore over a newer active lease', async () => {
|
|
79
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-session-'));
|
|
80
|
+
roots.push(root);
|
|
81
|
+
const store = new DdcEntryStore(root);
|
|
82
|
+
await store.write(entry());
|
|
83
|
+
|
|
84
|
+
const first = new DdcGenerationSession(root, { generation: 1 });
|
|
85
|
+
const firstCandidate = await first.stageEntry(entry());
|
|
86
|
+
const second = new DdcGenerationSession(root, { generation: 2 });
|
|
87
|
+
const secondCandidate = await second.stageEntry(entry());
|
|
88
|
+
|
|
89
|
+
await expect(first.commitEntry(firstCandidate, KEY)).resolves.toMatchObject({
|
|
90
|
+
result: 'stale',
|
|
91
|
+
});
|
|
92
|
+
await expect(first.restoreEntry(firstCandidate)).resolves.toMatchObject({
|
|
93
|
+
result: 'not-owner',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await expect(second.commitEntry(secondCandidate, KEY)).resolves.toMatchObject({
|
|
97
|
+
result: 'current',
|
|
98
|
+
});
|
|
99
|
+
await expect(first.restoreEntry(firstCandidate)).resolves.toMatchObject({
|
|
100
|
+
result: 'not-owner',
|
|
101
|
+
});
|
|
102
|
+
await expect(second.inspect(GUID, KEY)).resolves.toMatchObject({
|
|
103
|
+
state: 'current',
|
|
104
|
+
currentKey: KEY,
|
|
105
|
+
});
|
|
106
|
+
await first.close();
|
|
107
|
+
await second.close();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('restores an own active candidate without regressing revision or generation', async () => {
|
|
111
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-session-'));
|
|
112
|
+
roots.push(root);
|
|
113
|
+
const lifecycle = new DdcLifecycle(root);
|
|
114
|
+
const { lease, previousHead } = await lifecycle.beginWithSnapshot(GUID, KEY);
|
|
115
|
+
|
|
116
|
+
expect('activeLease' in previousHead).toBe(false);
|
|
117
|
+
await expect(lifecycle.restoreIfCurrent(previousHead, lease)).resolves.toMatchObject({
|
|
118
|
+
result: 'restored',
|
|
119
|
+
revision: 1,
|
|
120
|
+
generation: lease.generation,
|
|
121
|
+
});
|
|
122
|
+
await expect(lifecycle.inspect(GUID, KEY)).resolves.toMatchObject({
|
|
123
|
+
state: 'missing',
|
|
124
|
+
revision: 1,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const next = await lifecycle.begin(GUID, KEY);
|
|
128
|
+
expect(next.expectedRevision).toBe(1);
|
|
129
|
+
expect(next.generation).toBeGreaterThan(lease.generation);
|
|
130
|
+
await lifecycle.close(next);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('restores an own terminal mutation while preserving the accepted head', async () => {
|
|
134
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-session-'));
|
|
135
|
+
roots.push(root);
|
|
136
|
+
const store = new DdcEntryStore(root);
|
|
137
|
+
await store.write(entry(KEY));
|
|
138
|
+
const lifecycle = new DdcLifecycle(root);
|
|
139
|
+
const accepted = await lifecycle.begin(GUID, KEY);
|
|
140
|
+
await expect(lifecycle.commit(accepted, KEY)).resolves.toMatchObject({ result: 'current' });
|
|
141
|
+
|
|
142
|
+
const { lease, previousHead } = await lifecycle.beginWithSnapshot(GUID, KEY_B);
|
|
143
|
+
await store.write(entry(KEY_B));
|
|
144
|
+
const committed = await lifecycle.commit(lease, KEY_B);
|
|
145
|
+
expect(committed.restoreFence).toMatchObject({ outcome: 'current' });
|
|
146
|
+
await expect(
|
|
147
|
+
lifecycle.restoreIfCurrent(previousHead, lease, committed.restoreFence),
|
|
148
|
+
).resolves.toMatchObject({ result: 'restored' });
|
|
149
|
+
await expect(lifecycle.inspect(GUID, KEY_B)).resolves.toMatchObject({
|
|
150
|
+
state: 'stale',
|
|
151
|
+
currentKey: KEY,
|
|
152
|
+
revision: (committed.revision ?? 0) + 1,
|
|
153
|
+
generation: lease.generation,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('rolls back a newer cooking candidate without reviving its superseded writer', async () => {
|
|
158
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-session-'));
|
|
159
|
+
roots.push(root);
|
|
160
|
+
const store = new DdcEntryStore(root);
|
|
161
|
+
await store.write(entry(KEY));
|
|
162
|
+
const lifecycle = new DdcLifecycle(root);
|
|
163
|
+
const accepted = await lifecycle.begin(GUID, KEY);
|
|
164
|
+
await lifecycle.commit(accepted, KEY);
|
|
165
|
+
|
|
166
|
+
const first = new DdcGenerationSession(root, { generation: 1 });
|
|
167
|
+
const firstCandidate = await first.stageEntry(entry(KEY_B));
|
|
168
|
+
const second = new DdcGenerationSession(root, { generation: 2 });
|
|
169
|
+
const secondCandidate = await second.stageEntry(entry(KEY_C));
|
|
170
|
+
|
|
171
|
+
await expect(second.restoreEntry(secondCandidate)).resolves.toMatchObject({
|
|
172
|
+
result: 'restored',
|
|
173
|
+
});
|
|
174
|
+
await expect(first.restoreEntry(firstCandidate)).resolves.toMatchObject({
|
|
175
|
+
result: 'not-owner',
|
|
176
|
+
});
|
|
177
|
+
await expect(second.inspect(GUID, KEY_C)).resolves.toMatchObject({
|
|
178
|
+
state: 'stale',
|
|
179
|
+
currentKey: KEY,
|
|
180
|
+
});
|
|
181
|
+
await first.close();
|
|
182
|
+
await second.close();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('fences a failed terminal mutation before restoring accepted content', async () => {
|
|
186
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-session-'));
|
|
187
|
+
roots.push(root);
|
|
188
|
+
const lifecycle = new DdcLifecycle(root);
|
|
189
|
+
const { lease, previousHead } = await lifecycle.beginWithSnapshot(GUID, KEY);
|
|
190
|
+
const failed = await lifecycle.fail(lease, { code: 'producer-failed', detail: 'fixture' });
|
|
191
|
+
|
|
192
|
+
expect(failed).toMatchObject({ outcome: 'failed', revision: 1 });
|
|
193
|
+
await expect(lifecycle.restoreIfCurrent(previousHead, lease, failed)).resolves.toMatchObject({
|
|
194
|
+
result: 'restored',
|
|
195
|
+
revision: 2,
|
|
196
|
+
});
|
|
197
|
+
await expect(lifecycle.inspect(GUID, KEY)).resolves.toMatchObject({
|
|
198
|
+
state: 'missing',
|
|
199
|
+
revision: 2,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
74
202
|
});
|
|
@@ -9,15 +9,17 @@ const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
9
9
|
const entryModule = join(sourceRoot, 'entry-store.ts');
|
|
10
10
|
const lifecycleModule = join(sourceRoot, 'lifecycle.ts');
|
|
11
11
|
const guid = '019e3969-1d48-7c3b-ac24-6d68f457065f';
|
|
12
|
+
const secondGuid = '019e3969-1d48-7c3b-ac24-6d68f457065e';
|
|
12
13
|
const key = 'a'.repeat(64);
|
|
13
14
|
|
|
14
15
|
function runWorker(
|
|
15
16
|
root: string,
|
|
16
17
|
source: string,
|
|
17
18
|
args: readonly string[] = [],
|
|
19
|
+
workerGuid = guid,
|
|
18
20
|
): Promise<Record<string, unknown>> {
|
|
19
21
|
return new Promise((resolveWorker, reject) => {
|
|
20
|
-
const child = spawnDdcWorker(source, [lifecycleModule, root,
|
|
22
|
+
const child = spawnDdcWorker(source, [lifecycleModule, root, workerGuid, key, ...args]);
|
|
21
23
|
let output = '';
|
|
22
24
|
let error = '';
|
|
23
25
|
child.stdout.on('data', (chunk: Buffer) => (output += chunk.toString()));
|
|
@@ -78,6 +80,24 @@ describe('DDC multiprocess lifecycle', () => {
|
|
|
78
80
|
expect(second.generation).toBeGreaterThan(first.generation as number);
|
|
79
81
|
});
|
|
80
82
|
|
|
83
|
+
it('serializes generation allocation across independent asset heads', async () => {
|
|
84
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-multiprocess-generation-'));
|
|
85
|
+
roots.push(root);
|
|
86
|
+
const script = `
|
|
87
|
+
const { DdcLifecycle } = await load(process.argv[1]);
|
|
88
|
+
const lease = await new DdcLifecycle(process.argv[2]).begin(process.argv[3], process.argv[4]);
|
|
89
|
+
console.log(JSON.stringify({ generation: lease.generation }));
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const [first, second] = await Promise.all([
|
|
93
|
+
runWorker(root, script, [], guid),
|
|
94
|
+
runWorker(root, script, [], secondGuid),
|
|
95
|
+
]);
|
|
96
|
+
expect(first.generation).toEqual(expect.any(Number));
|
|
97
|
+
expect(second.generation).toEqual(expect.any(Number));
|
|
98
|
+
expect(first.generation).not.toBe(second.generation);
|
|
99
|
+
});
|
|
100
|
+
|
|
81
101
|
it('rejects the stale writer instead of silently overwriting current', async () => {
|
|
82
102
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-ddc-multiprocess-head-'));
|
|
83
103
|
roots.push(root);
|
package/src/index.ts
CHANGED
|
@@ -41,11 +41,15 @@ export {
|
|
|
41
41
|
resolveDdcLayout,
|
|
42
42
|
} from './layout.js';
|
|
43
43
|
export {
|
|
44
|
+
type DdcBeginResult,
|
|
44
45
|
type DdcCommitResult,
|
|
45
46
|
type DdcHead,
|
|
46
47
|
type DdcLease,
|
|
47
48
|
DdcLifecycle,
|
|
48
49
|
type DdcLifecycleState,
|
|
50
|
+
type DdcRestoreFence,
|
|
51
|
+
type DdcRestoreResult,
|
|
52
|
+
type DdcRollbackSnapshot,
|
|
49
53
|
} from './lifecycle.js';
|
|
50
54
|
export {
|
|
51
55
|
type AcceptedPublicationCandidate,
|