instar 1.3.719 → 1.3.721
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +213 -31
- package/dist/commands/server.js.map +1 -1
- package/dist/core/CoherenceJournal.d.ts.map +1 -1
- package/dist/core/CoherenceJournal.js +9 -1
- package/dist/core/CoherenceJournal.js.map +1 -1
- package/dist/core/CoherenceJournalReader.d.ts +56 -0
- package/dist/core/CoherenceJournalReader.d.ts.map +1 -1
- package/dist/core/CoherenceJournalReader.js +148 -0
- package/dist/core/CoherenceJournalReader.js.map +1 -1
- package/dist/core/LeaseAcquisitionTrigger.d.ts +35 -0
- package/dist/core/LeaseAcquisitionTrigger.d.ts.map +1 -0
- package/dist/core/LeaseAcquisitionTrigger.js +53 -0
- package/dist/core/LeaseAcquisitionTrigger.js.map +1 -0
- package/dist/core/OwnershipReconciler.d.ts +106 -2
- package/dist/core/OwnershipReconciler.d.ts.map +1 -1
- package/dist/core/OwnershipReconciler.js +265 -12
- package/dist/core/OwnershipReconciler.js.map +1 -1
- package/dist/core/PlacementExecutor.d.ts +14 -1
- package/dist/core/PlacementExecutor.d.ts.map +1 -1
- package/dist/core/PlacementExecutor.js +17 -2
- package/dist/core/PlacementExecutor.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +5 -3
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +33 -7
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SustainedOnlineTracker.d.ts +47 -0
- package/dist/core/SustainedOnlineTracker.d.ts.map +1 -0
- package/dist/core/SustainedOnlineTracker.js +67 -0
- package/dist/core/SustainedOnlineTracker.js.map +1 -0
- package/dist/core/TopicPinFoldView.d.ts +138 -0
- package/dist/core/TopicPinFoldView.d.ts.map +1 -0
- package/dist/core/TopicPinFoldView.js +235 -0
- package/dist/core/TopicPinFoldView.js.map +1 -0
- package/dist/core/TopicPinMutation.d.ts +46 -0
- package/dist/core/TopicPinMutation.d.ts.map +1 -0
- package/dist/core/TopicPinMutation.js +66 -0
- package/dist/core/TopicPinMutation.js.map +1 -0
- package/dist/core/TopicPinReplicatedStore.d.ts +3 -0
- package/dist/core/TopicPinReplicatedStore.d.ts.map +1 -1
- package/dist/core/TopicPinReplicatedStore.js +5 -0
- package/dist/core/TopicPinReplicatedStore.js.map +1 -1
- package/dist/core/TopicPinSkewQuarantine.d.ts +44 -0
- package/dist/core/TopicPinSkewQuarantine.d.ts.map +1 -0
- package/dist/core/TopicPinSkewQuarantine.js +136 -0
- package/dist/core/TopicPinSkewQuarantine.js.map +1 -0
- package/dist/core/TopicPlacementPinStore.d.ts +29 -2
- package/dist/core/TopicPlacementPinStore.d.ts.map +1 -1
- package/dist/core/TopicPlacementPinStore.js +23 -5
- package/dist/core/TopicPlacementPinStore.js.map +1 -1
- package/dist/core/types.d.ts +29 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/SessionActivitySentinel.d.ts.map +1 -1
- package/dist/monitoring/SessionActivitySentinel.js +3 -0
- package/dist/monitoring/SessionActivitySentinel.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +46 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +4 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +2 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +160 -15
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/state-coherence-registry.json +18 -0
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.720.md +125 -0
- package/upgrades/1.3.721.md +55 -0
- package/upgrades/eli16/digest-safety-prompt.eli16.md +61 -0
- package/upgrades/side-effects/digest-safety-prompt.md +112 -0
- package/upgrades/side-effects/u4-1-pin-persistence.md +49 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SustainedOnlineTracker — U4.1 §2E hysteresis input ("pin persistence",
|
|
3
|
+
* docs/specs/u4-1-pin-persistence.md).
|
|
4
|
+
*
|
|
5
|
+
* Answers ONE question for pin-driven actuation: "has this machine been
|
|
6
|
+
* CONTINUOUSLY online for at least the sustained window?" — the anti-flap
|
|
7
|
+
* gate that keeps a returning-but-flapping pinned machine from triggering
|
|
8
|
+
* transfer ping-pong (mirror of U4.4's hysteresis), and keeps Case-A
|
|
9
|
+
* cooperative transfers from initiating toward a machine that only just
|
|
10
|
+
* blinked online (R-r2-2: the transfer→abort churn loop).
|
|
11
|
+
*
|
|
12
|
+
* Feeding: `observe(machines)` is called on the reconciler tick cadence with
|
|
13
|
+
* the live pool view. A machine seen online starts (or continues) its
|
|
14
|
+
* online-since clock; seen offline/absent resets it.
|
|
15
|
+
*
|
|
16
|
+
* Boot honesty (deliberate fail-open): the tracker cannot distinguish
|
|
17
|
+
* "stable for hours" from "just flapped on" until it has watched for at
|
|
18
|
+
* least one full window. While its own observation history is younger than
|
|
19
|
+
* the queried window, `sustainedOnline` falls back to the CURRENT online
|
|
20
|
+
* bit (today's behavior) — otherwise every pinned placement would queue for
|
|
21
|
+
* the window length after every boot, a routine-latency regression the
|
|
22
|
+
* anti-flap gate was never meant to buy. After the tracker has ≥ window of
|
|
23
|
+
* history the gate is fully meaningful.
|
|
24
|
+
*/
|
|
25
|
+
export interface SustainedOnlineView {
|
|
26
|
+
machineId: string;
|
|
27
|
+
online: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare class SustainedOnlineTracker {
|
|
30
|
+
/** machineId → epoch-ms the machine was first seen online in its CURRENT unbroken online run. */
|
|
31
|
+
private readonly onlineSince;
|
|
32
|
+
private readonly now;
|
|
33
|
+
/** When this tracker started observing (the boot fail-open boundary). */
|
|
34
|
+
private readonly trackingSince;
|
|
35
|
+
constructor(deps?: {
|
|
36
|
+
now?: () => number;
|
|
37
|
+
});
|
|
38
|
+
/** Fold one live pool view into the online-run clocks. */
|
|
39
|
+
observe(machines: ReadonlyArray<SustainedOnlineView>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Has `machineId` been continuously online for ≥ `windowMs`?
|
|
42
|
+
* Boot fail-open: while the tracker's own history is younger than the
|
|
43
|
+
* window, this degrades to "is it online right now" (see header).
|
|
44
|
+
*/
|
|
45
|
+
sustainedOnline(machineId: string, windowMs: number): boolean;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=SustainedOnlineTracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SustainedOnlineTracker.d.ts","sourceRoot":"","sources":["../../src/core/SustainedOnlineTracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,sBAAsB;IACjC,iGAAiG;IACjG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAK7C,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAc3D;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;CAO9D"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SustainedOnlineTracker — U4.1 §2E hysteresis input ("pin persistence",
|
|
3
|
+
* docs/specs/u4-1-pin-persistence.md).
|
|
4
|
+
*
|
|
5
|
+
* Answers ONE question for pin-driven actuation: "has this machine been
|
|
6
|
+
* CONTINUOUSLY online for at least the sustained window?" — the anti-flap
|
|
7
|
+
* gate that keeps a returning-but-flapping pinned machine from triggering
|
|
8
|
+
* transfer ping-pong (mirror of U4.4's hysteresis), and keeps Case-A
|
|
9
|
+
* cooperative transfers from initiating toward a machine that only just
|
|
10
|
+
* blinked online (R-r2-2: the transfer→abort churn loop).
|
|
11
|
+
*
|
|
12
|
+
* Feeding: `observe(machines)` is called on the reconciler tick cadence with
|
|
13
|
+
* the live pool view. A machine seen online starts (or continues) its
|
|
14
|
+
* online-since clock; seen offline/absent resets it.
|
|
15
|
+
*
|
|
16
|
+
* Boot honesty (deliberate fail-open): the tracker cannot distinguish
|
|
17
|
+
* "stable for hours" from "just flapped on" until it has watched for at
|
|
18
|
+
* least one full window. While its own observation history is younger than
|
|
19
|
+
* the queried window, `sustainedOnline` falls back to the CURRENT online
|
|
20
|
+
* bit (today's behavior) — otherwise every pinned placement would queue for
|
|
21
|
+
* the window length after every boot, a routine-latency regression the
|
|
22
|
+
* anti-flap gate was never meant to buy. After the tracker has ≥ window of
|
|
23
|
+
* history the gate is fully meaningful.
|
|
24
|
+
*/
|
|
25
|
+
export class SustainedOnlineTracker {
|
|
26
|
+
/** machineId → epoch-ms the machine was first seen online in its CURRENT unbroken online run. */
|
|
27
|
+
onlineSince = new Map();
|
|
28
|
+
now;
|
|
29
|
+
/** When this tracker started observing (the boot fail-open boundary). */
|
|
30
|
+
trackingSince;
|
|
31
|
+
constructor(deps = {}) {
|
|
32
|
+
this.now = deps.now ?? Date.now;
|
|
33
|
+
this.trackingSince = this.now();
|
|
34
|
+
}
|
|
35
|
+
/** Fold one live pool view into the online-run clocks. */
|
|
36
|
+
observe(machines) {
|
|
37
|
+
const t = this.now();
|
|
38
|
+
const seenOnline = new Set();
|
|
39
|
+
for (const m of machines) {
|
|
40
|
+
if (!m.online)
|
|
41
|
+
continue;
|
|
42
|
+
seenOnline.add(m.machineId);
|
|
43
|
+
if (!this.onlineSince.has(m.machineId))
|
|
44
|
+
this.onlineSince.set(m.machineId, t);
|
|
45
|
+
}
|
|
46
|
+
// A machine reported offline — or absent from the view — breaks its run.
|
|
47
|
+
for (const id of [...this.onlineSince.keys()]) {
|
|
48
|
+
if (!seenOnline.has(id))
|
|
49
|
+
this.onlineSince.delete(id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Has `machineId` been continuously online for ≥ `windowMs`?
|
|
54
|
+
* Boot fail-open: while the tracker's own history is younger than the
|
|
55
|
+
* window, this degrades to "is it online right now" (see header).
|
|
56
|
+
*/
|
|
57
|
+
sustainedOnline(machineId, windowMs) {
|
|
58
|
+
const since = this.onlineSince.get(machineId);
|
|
59
|
+
if (since === undefined)
|
|
60
|
+
return false; // not online right now — never sustained
|
|
61
|
+
const t = this.now();
|
|
62
|
+
if (t - this.trackingSince < windowMs)
|
|
63
|
+
return true; // fail-open boot window (online now)
|
|
64
|
+
return t - since >= windowMs;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=SustainedOnlineTracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SustainedOnlineTracker.js","sourceRoot":"","sources":["../../src/core/SustainedOnlineTracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,MAAM,OAAO,sBAAsB;IACjC,iGAAiG;IAChF,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,GAAG,CAAe;IACnC,yEAAyE;IACxD,aAAa,CAAS;IAEvC,YAAY,OAA+B,EAAE;QAC3C,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,0DAA0D;IAC1D,OAAO,CAAC,QAA4C;QAClD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,CAAC,MAAM;gBAAE,SAAS;YACxB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,yEAAyE;QACzE,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,SAAiB,EAAE,QAAgB;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,CAAC,yCAAyC;QAChF,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,GAAG,QAAQ;YAAE,OAAO,IAAI,CAAC,CAAC,qCAAqC;QACzF,OAAO,CAAC,GAAG,KAAK,IAAI,QAAQ,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicPinFoldView — the STATEFUL answer-complete advisory-pin read
|
|
3
|
+
* (U4.1 §2C, docs/specs/u4-1-pin-persistence.md; R-r2-3 / R-r2-1 / R-r3-1 /
|
|
4
|
+
* R-r3-2).
|
|
5
|
+
*
|
|
6
|
+
* Replaces the `query({ kind: 'topic-pin-record', limit: 2000 })` newest-tail
|
|
7
|
+
* read (silently clamped to 500 by `READER_MAX_LIMIT`) with:
|
|
8
|
+
*
|
|
9
|
+
* 1. A BOOT-TIME FULL-STREAM FOLD over every retained `topic-pin-record`
|
|
10
|
+
* entry — active file + archives, own stream AND every peer-replica
|
|
11
|
+
* stream — into a per-key latest map by HLC (tombstone-respecting: a
|
|
12
|
+
* winning delete stays in the map so a late-arriving OLDER put can never
|
|
13
|
+
* resurrect the pin).
|
|
14
|
+
* 2. An INCREMENTAL OFFSET-TRACKED TAIL after boot: each refresh re-scans
|
|
15
|
+
* only appended bytes (TokenLedgerPoller pattern); an unchanged journal
|
|
16
|
+
* costs zero re-read; re-scans are idempotent (same records → same
|
|
17
|
+
* winners).
|
|
18
|
+
* 3. The HLC SKEW GATE (fixes defect 6; R-r2-1): every record HLC passes
|
|
19
|
+
* through the existing `HybridLogicalClock.receive()` contract (clamped
|
|
20
|
+
* maxDriftMs). A future-skewed record is REJECTED FROM THE FOLD — never
|
|
21
|
+
* merged, never able to win `compareHlc` — and quarantined STICKILY +
|
|
22
|
+
* DURABLY via TopicPinSkewQuarantine (R-r3-2: the moving wall-clock
|
|
23
|
+
* reference must never silently un-quarantine it). The fold-side gate is
|
|
24
|
+
* the SOLE skew-exclusion authority (R-r3-1) — the JournalSyncApplier
|
|
25
|
+
* deliberately ACCEPTS-AND-PERSISTS a skewed record, because its only
|
|
26
|
+
* per-entry refusal path would suspect-halt the peer's ENTIRE stream.
|
|
27
|
+
* 4. The FOLD BYTE-GUARD (`ws13FoldMaxBytes`, R-r3-3): on breach the fold
|
|
28
|
+
* reads newest-first up to the budget and LOUDLY escalates (ONE deduped
|
|
29
|
+
* `u41:pin-fold-truncated` item naming the unfolded ranges) — never a
|
|
30
|
+
* silent truncation. The episode closes when a full fold completes
|
|
31
|
+
* within budget.
|
|
32
|
+
*
|
|
33
|
+
* Read-only over journal bytes; it actuates NOTHING (consumers are wired via
|
|
34
|
+
* server.ts closures exactly like the old tail read — the actuation-ban lint
|
|
35
|
+
* posture is unchanged).
|
|
36
|
+
*/
|
|
37
|
+
import { type HlcTimestamp } from './HybridLogicalClock.js';
|
|
38
|
+
import { type MergedReplicatedPin } from './TopicPinReplicatedStore.js';
|
|
39
|
+
import type { PinFoldOffsets, PinFoldResult } from './CoherenceJournalReader.js';
|
|
40
|
+
import type { TopicPinSkewQuarantine } from './TopicPinSkewQuarantine.js';
|
|
41
|
+
import type { TopicPlacement } from './PlacementExecutor.js';
|
|
42
|
+
/** The reader seam (CoherenceJournalReader.foldPinRecords — injectable for tests). */
|
|
43
|
+
export interface PinFoldReader {
|
|
44
|
+
foldPinRecords(opts: {
|
|
45
|
+
priorOffsets?: PinFoldOffsets;
|
|
46
|
+
maxBytes?: number;
|
|
47
|
+
}): PinFoldResult;
|
|
48
|
+
}
|
|
49
|
+
export interface TopicPinFoldViewDeps {
|
|
50
|
+
reader: PinFoldReader;
|
|
51
|
+
quarantine: TopicPinSkewQuarantine;
|
|
52
|
+
/** This machine's mesh id (late-bound; used as the fold clock's node id). */
|
|
53
|
+
selfNode: () => string | null;
|
|
54
|
+
/** The clamped HLC drift ceiling (§2C reuses the existing knob — no new key). */
|
|
55
|
+
maxDriftMs?: number;
|
|
56
|
+
/** ws13FoldMaxBytes, read live (default 64MB upstream). */
|
|
57
|
+
foldMaxBytes?: () => number;
|
|
58
|
+
/** Optional observed pool-relative physical-time floor (heartbeat median). */
|
|
59
|
+
poolReference?: () => number | undefined;
|
|
60
|
+
now?: () => number;
|
|
61
|
+
/** Fired ONCE per newly-quarantined record (upstream dedupes per-origin, P17). */
|
|
62
|
+
onSkewQuarantined?: (rec: {
|
|
63
|
+
key: string;
|
|
64
|
+
hlc: HlcTimestamp;
|
|
65
|
+
origin: string;
|
|
66
|
+
}) => void;
|
|
67
|
+
/** Fired ONCE per truncation EPISODE (byte-guard breach) with the unfolded ranges. */
|
|
68
|
+
onFoldTruncated?: (unfolded: Array<{
|
|
69
|
+
file: string;
|
|
70
|
+
fromByte: number;
|
|
71
|
+
toByte: number;
|
|
72
|
+
}>) => void;
|
|
73
|
+
/** Fired when a full fold completes within budget after a breach (episode close). */
|
|
74
|
+
onFoldRecovered?: () => void;
|
|
75
|
+
log?: (msg: string) => void;
|
|
76
|
+
}
|
|
77
|
+
export declare class TopicPinFoldView {
|
|
78
|
+
private readonly d;
|
|
79
|
+
/** Per-key HLC winner (puts AND tombstones — tombstone-respecting by construction). */
|
|
80
|
+
private readonly winners;
|
|
81
|
+
private offsets;
|
|
82
|
+
private clock;
|
|
83
|
+
private truncationEpisodeOpen;
|
|
84
|
+
private lastFoldAtMs;
|
|
85
|
+
private lastScannedBytes;
|
|
86
|
+
private totalFolds;
|
|
87
|
+
constructor(deps: TopicPinFoldViewDeps);
|
|
88
|
+
private now;
|
|
89
|
+
private foldClock;
|
|
90
|
+
/**
|
|
91
|
+
* Fold newly-appended journal bytes into the winner map. The FIRST call is
|
|
92
|
+
* the boot-time full-stream fold (offsets start empty); later calls are the
|
|
93
|
+
* incremental offset-tracked tail. Never throws (a fold fault leaves the
|
|
94
|
+
* last-good map serving — the next refresh retries).
|
|
95
|
+
*/
|
|
96
|
+
refresh(): void;
|
|
97
|
+
/**
|
|
98
|
+
* The merged advisory pins (the §2C second pass — mirrors
|
|
99
|
+
* `mergeUnionToPins`): a winning tombstone or `pinned:false` put yields NO
|
|
100
|
+
* pin; a valid winning put yields one advisory pin carrying its HLC.
|
|
101
|
+
*/
|
|
102
|
+
pins(): Map<number, MergedReplicatedPin>;
|
|
103
|
+
/** The fold pin as placement metadata for NEW placements (§2D seeding): the
|
|
104
|
+
* hard-pin path handles an offline target honestly (queued, never re-routed),
|
|
105
|
+
* so the pin is surfaced whenever a valid winner exists. */
|
|
106
|
+
asTopicMetadata(sessionKey: string): TopicPlacement | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* §2D placement seeding with the FULL local-vs-replicated resolution (the
|
|
109
|
+
* N3 rule at the placement door): the LOCAL pin and the fold winner compare
|
|
110
|
+
* by HLC — a stale local pin never masks a fresher replicated move-intent,
|
|
111
|
+
* and a fresher replicated TOMBSTONE suppresses a stale local pin (the
|
|
112
|
+
* defect-2 fix applied to NEW placements, not just the reconciler). A local
|
|
113
|
+
* pin without an HLC uses the documented `updatedAt` fallback derivation.
|
|
114
|
+
*/
|
|
115
|
+
effectiveTopicMetadata(sessionKey: string, local: {
|
|
116
|
+
preferredMachine: string;
|
|
117
|
+
pinned: boolean;
|
|
118
|
+
updatedAt: string;
|
|
119
|
+
hlc?: HlcTimestamp;
|
|
120
|
+
} | null): TopicPlacement | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Force the NEXT refresh to be a full-stream re-fold (offsets + winners
|
|
123
|
+
* cleared). Used by the explicit per-record re-admission surface (R-r4-1):
|
|
124
|
+
* a re-admitted record's bytes were already consumed by the incremental
|
|
125
|
+
* tail, so only a full re-fold can bring it back into the comparison.
|
|
126
|
+
*/
|
|
127
|
+
resetFold(): void;
|
|
128
|
+
/** Read-only observability (route/status surfaces). */
|
|
129
|
+
status(): {
|
|
130
|
+
lastFoldAt: string | null;
|
|
131
|
+
totalFolds: number;
|
|
132
|
+
lastScannedBytes: number;
|
|
133
|
+
recordKeys: number;
|
|
134
|
+
truncationEpisodeOpen: boolean;
|
|
135
|
+
quarantined: number;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=TopicPinFoldView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopicPinFoldView.d.ts","sourceRoot":"","sources":["../../src/core/TopicPinFoldView.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicPinFoldView — the STATEFUL answer-complete advisory-pin read
|
|
3
|
+
* (U4.1 §2C, docs/specs/u4-1-pin-persistence.md; R-r2-3 / R-r2-1 / R-r3-1 /
|
|
4
|
+
* R-r3-2).
|
|
5
|
+
*
|
|
6
|
+
* Replaces the `query({ kind: 'topic-pin-record', limit: 2000 })` newest-tail
|
|
7
|
+
* read (silently clamped to 500 by `READER_MAX_LIMIT`) with:
|
|
8
|
+
*
|
|
9
|
+
* 1. A BOOT-TIME FULL-STREAM FOLD over every retained `topic-pin-record`
|
|
10
|
+
* entry — active file + archives, own stream AND every peer-replica
|
|
11
|
+
* stream — into a per-key latest map by HLC (tombstone-respecting: a
|
|
12
|
+
* winning delete stays in the map so a late-arriving OLDER put can never
|
|
13
|
+
* resurrect the pin).
|
|
14
|
+
* 2. An INCREMENTAL OFFSET-TRACKED TAIL after boot: each refresh re-scans
|
|
15
|
+
* only appended bytes (TokenLedgerPoller pattern); an unchanged journal
|
|
16
|
+
* costs zero re-read; re-scans are idempotent (same records → same
|
|
17
|
+
* winners).
|
|
18
|
+
* 3. The HLC SKEW GATE (fixes defect 6; R-r2-1): every record HLC passes
|
|
19
|
+
* through the existing `HybridLogicalClock.receive()` contract (clamped
|
|
20
|
+
* maxDriftMs). A future-skewed record is REJECTED FROM THE FOLD — never
|
|
21
|
+
* merged, never able to win `compareHlc` — and quarantined STICKILY +
|
|
22
|
+
* DURABLY via TopicPinSkewQuarantine (R-r3-2: the moving wall-clock
|
|
23
|
+
* reference must never silently un-quarantine it). The fold-side gate is
|
|
24
|
+
* the SOLE skew-exclusion authority (R-r3-1) — the JournalSyncApplier
|
|
25
|
+
* deliberately ACCEPTS-AND-PERSISTS a skewed record, because its only
|
|
26
|
+
* per-entry refusal path would suspect-halt the peer's ENTIRE stream.
|
|
27
|
+
* 4. The FOLD BYTE-GUARD (`ws13FoldMaxBytes`, R-r3-3): on breach the fold
|
|
28
|
+
* reads newest-first up to the budget and LOUDLY escalates (ONE deduped
|
|
29
|
+
* `u41:pin-fold-truncated` item naming the unfolded ranges) — never a
|
|
30
|
+
* silent truncation. The episode closes when a full fold completes
|
|
31
|
+
* within budget.
|
|
32
|
+
*
|
|
33
|
+
* Read-only over journal bytes; it actuates NOTHING (consumers are wired via
|
|
34
|
+
* server.ts closures exactly like the old tail read — the actuation-ban lint
|
|
35
|
+
* posture is unchanged).
|
|
36
|
+
*/
|
|
37
|
+
import { HybridLogicalClock, coerceHlc } from './HybridLogicalClock.js';
|
|
38
|
+
import { compareHlc, isValidPinMachineId } from './TopicPinReplicatedStore.js';
|
|
39
|
+
export class TopicPinFoldView {
|
|
40
|
+
d;
|
|
41
|
+
/** Per-key HLC winner (puts AND tombstones — tombstone-respecting by construction). */
|
|
42
|
+
winners = new Map();
|
|
43
|
+
offsets = {};
|
|
44
|
+
clock = null;
|
|
45
|
+
truncationEpisodeOpen = false;
|
|
46
|
+
lastFoldAtMs = 0;
|
|
47
|
+
lastScannedBytes = 0;
|
|
48
|
+
totalFolds = 0;
|
|
49
|
+
constructor(deps) {
|
|
50
|
+
this.d = deps;
|
|
51
|
+
}
|
|
52
|
+
now() {
|
|
53
|
+
return (this.d.now ?? Date.now)();
|
|
54
|
+
}
|
|
55
|
+
foldClock() {
|
|
56
|
+
if (!this.clock) {
|
|
57
|
+
this.clock = new HybridLogicalClock({
|
|
58
|
+
node: this.d.selfNode() ?? 'pin-fold',
|
|
59
|
+
now: this.d.now ?? Date.now,
|
|
60
|
+
...(this.d.maxDriftMs !== undefined ? { maxDriftMs: this.d.maxDriftMs } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return this.clock;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Fold newly-appended journal bytes into the winner map. The FIRST call is
|
|
67
|
+
* the boot-time full-stream fold (offsets start empty); later calls are the
|
|
68
|
+
* incremental offset-tracked tail. Never throws (a fold fault leaves the
|
|
69
|
+
* last-good map serving — the next refresh retries).
|
|
70
|
+
*/
|
|
71
|
+
refresh() {
|
|
72
|
+
let res;
|
|
73
|
+
try {
|
|
74
|
+
res = this.d.reader.foldPinRecords({
|
|
75
|
+
priorOffsets: this.offsets,
|
|
76
|
+
...(this.d.foldMaxBytes ? { maxBytes: this.d.foldMaxBytes() } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
this.d.log?.(`[TopicPinFoldView] fold failed (serving last-good map): ${err instanceof Error ? err.message : String(err)}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.offsets = res.offsets;
|
|
84
|
+
this.lastFoldAtMs = this.now();
|
|
85
|
+
this.lastScannedBytes = res.scannedBytes;
|
|
86
|
+
this.totalFolds++;
|
|
87
|
+
const clock = this.foldClock();
|
|
88
|
+
for (const entry of res.entries) {
|
|
89
|
+
const recordKey = typeof entry.data.recordKey === 'string' ? entry.data.recordKey : null;
|
|
90
|
+
if (recordKey === null)
|
|
91
|
+
continue;
|
|
92
|
+
let hlc;
|
|
93
|
+
try {
|
|
94
|
+
hlc = coerceHlc(entry.data.hlc);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue; // malformed hlc — schema-reject class, never a winner
|
|
98
|
+
}
|
|
99
|
+
// Sticky exclusion FIRST (R-r3-2): a quarantined (key, hlc) stays excluded
|
|
100
|
+
// regardless of clock progress — the moving receive() reference must never
|
|
101
|
+
// silently re-admit it.
|
|
102
|
+
if (this.d.quarantine.has(recordKey, hlc))
|
|
103
|
+
continue;
|
|
104
|
+
// The skew gate (R-r2-1): the existing receive() contract. Rejection ⇒
|
|
105
|
+
// durable quarantine + loud (deduped upstream) escalation; NEVER merged.
|
|
106
|
+
const poolReference = this.d.poolReference?.();
|
|
107
|
+
const received = clock.receive(hlc, poolReference !== undefined ? { poolReference } : {});
|
|
108
|
+
if (received.rejected) {
|
|
109
|
+
const newlyAdded = this.d.quarantine.add({ key: recordKey, hlc, origin: entry.origin });
|
|
110
|
+
if (newlyAdded) {
|
|
111
|
+
this.d.log?.(`[TopicPinFoldView] skew-quarantined pin record key=${recordKey} origin=${entry.origin} (physical ${hlc.physical} > reference ${received.reference} + ${received.maxDriftMs}ms)`);
|
|
112
|
+
try {
|
|
113
|
+
this.d.onSkewQuarantined?.({ key: recordKey, hlc, origin: entry.origin });
|
|
114
|
+
}
|
|
115
|
+
catch { /* escalation is observability — never gates the fold */ }
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const cur = this.winners.get(recordKey);
|
|
120
|
+
if (!cur || compareHlc(hlc, cur.hlc) > 0) {
|
|
121
|
+
this.winners.set(recordKey, { data: entry.data, origin: entry.origin, hlc });
|
|
122
|
+
// Honest supersession (R-r3-2): a newer honest record makes older
|
|
123
|
+
// quarantined entries for the key dead by ordering — prune them.
|
|
124
|
+
try {
|
|
125
|
+
this.d.quarantine.pruneSuperseded(recordKey, hlc);
|
|
126
|
+
}
|
|
127
|
+
catch { /* prune is hygiene — never gates */ }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Byte-guard episode (R-r3-3): raise ONCE per episode; close on a clean full fold.
|
|
131
|
+
if (res.truncated) {
|
|
132
|
+
if (!this.truncationEpisodeOpen) {
|
|
133
|
+
this.truncationEpisodeOpen = true;
|
|
134
|
+
try {
|
|
135
|
+
this.d.onFoldTruncated?.(res.unfolded);
|
|
136
|
+
}
|
|
137
|
+
catch { /* escalation never gates */ }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (this.truncationEpisodeOpen) {
|
|
141
|
+
this.truncationEpisodeOpen = false;
|
|
142
|
+
try {
|
|
143
|
+
this.d.onFoldRecovered?.();
|
|
144
|
+
}
|
|
145
|
+
catch { /* episode close is observability */ }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The merged advisory pins (the §2C second pass — mirrors
|
|
150
|
+
* `mergeUnionToPins`): a winning tombstone or `pinned:false` put yields NO
|
|
151
|
+
* pin; a valid winning put yields one advisory pin carrying its HLC.
|
|
152
|
+
*/
|
|
153
|
+
pins() {
|
|
154
|
+
const out = new Map();
|
|
155
|
+
for (const [recordKey, w] of this.winners) {
|
|
156
|
+
if (w.data.op === 'delete')
|
|
157
|
+
continue;
|
|
158
|
+
if (w.data.pinned !== true)
|
|
159
|
+
continue;
|
|
160
|
+
const topic = Number(recordKey);
|
|
161
|
+
if (!Number.isFinite(topic))
|
|
162
|
+
continue;
|
|
163
|
+
if (!isValidPinMachineId(w.data.preferredMachine))
|
|
164
|
+
continue;
|
|
165
|
+
out.set(topic, { topic, preferredMachine: w.data.preferredMachine, pinned: true, origin: w.origin, hlc: w.hlc });
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/** The fold pin as placement metadata for NEW placements (§2D seeding): the
|
|
170
|
+
* hard-pin path handles an offline target honestly (queued, never re-routed),
|
|
171
|
+
* so the pin is surfaced whenever a valid winner exists. */
|
|
172
|
+
asTopicMetadata(sessionKey) {
|
|
173
|
+
const topic = Number(sessionKey);
|
|
174
|
+
if (!Number.isFinite(topic))
|
|
175
|
+
return undefined;
|
|
176
|
+
const pin = this.pins().get(topic);
|
|
177
|
+
return pin ? { preferredMachine: pin.preferredMachine, pinned: true } : undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* §2D placement seeding with the FULL local-vs-replicated resolution (the
|
|
181
|
+
* N3 rule at the placement door): the LOCAL pin and the fold winner compare
|
|
182
|
+
* by HLC — a stale local pin never masks a fresher replicated move-intent,
|
|
183
|
+
* and a fresher replicated TOMBSTONE suppresses a stale local pin (the
|
|
184
|
+
* defect-2 fix applied to NEW placements, not just the reconciler). A local
|
|
185
|
+
* pin without an HLC uses the documented `updatedAt` fallback derivation.
|
|
186
|
+
*/
|
|
187
|
+
effectiveTopicMetadata(sessionKey, local) {
|
|
188
|
+
const localMeta = local
|
|
189
|
+
? { preferredMachine: local.preferredMachine, pinned: local.pinned }
|
|
190
|
+
: undefined;
|
|
191
|
+
const topic = Number(sessionKey);
|
|
192
|
+
const winner = Number.isFinite(topic) ? this.winners.get(String(topic)) : undefined;
|
|
193
|
+
if (!winner)
|
|
194
|
+
return localMeta;
|
|
195
|
+
const localHlc = local
|
|
196
|
+
? (local.hlc ?? { physical: Date.parse(local.updatedAt) || 0, logical: 0, node: '' })
|
|
197
|
+
: null;
|
|
198
|
+
if (localHlc && compareHlc(localHlc, winner.hlc) >= 0)
|
|
199
|
+
return localMeta; // local is newer-or-equal
|
|
200
|
+
// The fold winner is newer: a tombstone / pinned:false resolves to NO pin
|
|
201
|
+
// (a stale local pin must not resurrect a cleared one); a valid put wins.
|
|
202
|
+
if (winner.data.op === 'delete' || winner.data.pinned !== true)
|
|
203
|
+
return undefined;
|
|
204
|
+
if (!isValidPinMachineId(winner.data.preferredMachine))
|
|
205
|
+
return localMeta;
|
|
206
|
+
return { preferredMachine: winner.data.preferredMachine, pinned: true };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Force the NEXT refresh to be a full-stream re-fold (offsets + winners
|
|
210
|
+
* cleared). Used by the explicit per-record re-admission surface (R-r4-1):
|
|
211
|
+
* a re-admitted record's bytes were already consumed by the incremental
|
|
212
|
+
* tail, so only a full re-fold can bring it back into the comparison.
|
|
213
|
+
*/
|
|
214
|
+
resetFold() {
|
|
215
|
+
this.offsets = {};
|
|
216
|
+
this.winners.clear();
|
|
217
|
+
}
|
|
218
|
+
/** Read-only observability (route/status surfaces). */
|
|
219
|
+
status() {
|
|
220
|
+
let quarantined = 0;
|
|
221
|
+
try {
|
|
222
|
+
quarantined = this.d.quarantine.all().length;
|
|
223
|
+
}
|
|
224
|
+
catch { /* best-effort */ }
|
|
225
|
+
return {
|
|
226
|
+
lastFoldAt: this.lastFoldAtMs ? new Date(this.lastFoldAtMs).toISOString() : null,
|
|
227
|
+
totalFolds: this.totalFolds,
|
|
228
|
+
lastScannedBytes: this.lastScannedBytes,
|
|
229
|
+
recordKeys: this.winners.size,
|
|
230
|
+
truncationEpisodeOpen: this.truncationEpisodeOpen,
|
|
231
|
+
quarantined,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=TopicPinFoldView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopicPinFoldView.js","sourceRoot":"","sources":["../../src/core/TopicPinFoldView.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicPinMutation — the ONE-HLC pin mutation funnel (U4.1 §2B,
|
|
3
|
+
* docs/specs/u4-1-pin-persistence.md).
|
|
4
|
+
*
|
|
5
|
+
* THE CONTRACT (R-r2): every pin mutation mints ONE HLC and stamps BOTH the
|
|
6
|
+
* local `TopicPlacementPinStore.set()/clear()` AND the replicated journal
|
|
7
|
+
* emit (`buildTopicPinPut` / `buildTopicPinTombstone`) with that same stamp.
|
|
8
|
+
* The transfer route previously called `set()` WITHOUT the `hlc` argument
|
|
9
|
+
* (the routes.ts gap) — the local-vs-replicated comparison then degraded to a
|
|
10
|
+
* derived wall-clock and HLC-highest-wins was a symbol, not the state.
|
|
11
|
+
*
|
|
12
|
+
* UNPIN (fixes defect 2 — the live un-pin propagation bug): `clearPin` wires
|
|
13
|
+
* the previously ZERO-caller `buildTopicPinTombstone` at the clear()
|
|
14
|
+
* chokepoint, so a cleared pin can never be resurrected by a stale replicated
|
|
15
|
+
* PUT (`effectivePins()` / the fold honor tombstones by HLC order). A later
|
|
16
|
+
* re-pin is the same key with a newer HLC — the store's documented model.
|
|
17
|
+
*
|
|
18
|
+
* The emitter mints the HLC (its `clock.tick()` is the author-side authority);
|
|
19
|
+
* the funnel CAPTURES the minted stamp from inside the build closure and hands
|
|
20
|
+
* it to the local store. When emission is dark (`ws13PinReplicate` off /
|
|
21
|
+
* single-machine) the build closure never runs → no stamp is minted → the
|
|
22
|
+
* local write proceeds without one (today's exact behavior; the reconciler's
|
|
23
|
+
* documented `updatedAt` fallback derivation covers it).
|
|
24
|
+
*/
|
|
25
|
+
import type { HlcTimestamp } from './HybridLogicalClock.js';
|
|
26
|
+
import type { TopicPlacementPinStore, TopicPinnedBy } from './TopicPlacementPinStore.js';
|
|
27
|
+
/** The emitter seam (ReplicatedRecordEmitter.emit — synchronous, never throws). */
|
|
28
|
+
export interface PinMutationEmitter {
|
|
29
|
+
emit(store: string, recordKey: string | null | undefined, build: (hlc: HlcTimestamp, origin: string, observed: HlcTimestamp | undefined) => Record<string, unknown> | null): void;
|
|
30
|
+
}
|
|
31
|
+
export interface PinMutationDeps {
|
|
32
|
+
pinStore: TopicPlacementPinStore;
|
|
33
|
+
/** Absent/null ⇒ replication un-wired (single-machine / early boot) — local-only write. */
|
|
34
|
+
emitter?: PinMutationEmitter | null;
|
|
35
|
+
now?: () => Date;
|
|
36
|
+
}
|
|
37
|
+
/** Pin a topic to a machine: ONE HLC stamps both the journal PUT and the local set(). */
|
|
38
|
+
export declare function setPinWithOneHlc(deps: PinMutationDeps, sessionKey: string, preferredMachine: string, pinned?: boolean, pinnedBy?: TopicPinnedBy): {
|
|
39
|
+
hlc?: HlcTimestamp;
|
|
40
|
+
};
|
|
41
|
+
/** Unpin a topic: the tombstone rides the SAME stamp the local clear is ordered by. */
|
|
42
|
+
export declare function clearPinWithTombstone(deps: PinMutationDeps, sessionKey: string): {
|
|
43
|
+
hadPin: boolean;
|
|
44
|
+
hlc?: HlcTimestamp;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=TopicPinMutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopicPinMutation.d.ts","sourceRoot":"","sources":["../../src/core/TopicPinMutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAQzF,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,IAAI,CACF,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,SAAS,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAC/G,IAAI,CAAC;CACT;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EACxB,MAAM,UAAO,EACb,QAAQ,CAAC,EAAE,aAAa,GACvB;IAAE,GAAG,CAAC,EAAE,YAAY,CAAA;CAAE,CAgBxB;AAED,uFAAuF;AACvF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,YAAY,CAAA;CAAE,CAkBzC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicPinMutation — the ONE-HLC pin mutation funnel (U4.1 §2B,
|
|
3
|
+
* docs/specs/u4-1-pin-persistence.md).
|
|
4
|
+
*
|
|
5
|
+
* THE CONTRACT (R-r2): every pin mutation mints ONE HLC and stamps BOTH the
|
|
6
|
+
* local `TopicPlacementPinStore.set()/clear()` AND the replicated journal
|
|
7
|
+
* emit (`buildTopicPinPut` / `buildTopicPinTombstone`) with that same stamp.
|
|
8
|
+
* The transfer route previously called `set()` WITHOUT the `hlc` argument
|
|
9
|
+
* (the routes.ts gap) — the local-vs-replicated comparison then degraded to a
|
|
10
|
+
* derived wall-clock and HLC-highest-wins was a symbol, not the state.
|
|
11
|
+
*
|
|
12
|
+
* UNPIN (fixes defect 2 — the live un-pin propagation bug): `clearPin` wires
|
|
13
|
+
* the previously ZERO-caller `buildTopicPinTombstone` at the clear()
|
|
14
|
+
* chokepoint, so a cleared pin can never be resurrected by a stale replicated
|
|
15
|
+
* PUT (`effectivePins()` / the fold honor tombstones by HLC order). A later
|
|
16
|
+
* re-pin is the same key with a newer HLC — the store's documented model.
|
|
17
|
+
*
|
|
18
|
+
* The emitter mints the HLC (its `clock.tick()` is the author-side authority);
|
|
19
|
+
* the funnel CAPTURES the minted stamp from inside the build closure and hands
|
|
20
|
+
* it to the local store. When emission is dark (`ws13PinReplicate` off /
|
|
21
|
+
* single-machine) the build closure never runs → no stamp is minted → the
|
|
22
|
+
* local write proceeds without one (today's exact behavior; the reconciler's
|
|
23
|
+
* documented `updatedAt` fallback derivation covers it).
|
|
24
|
+
*/
|
|
25
|
+
import { TOPIC_PIN_STORE_KEY, deriveTopicPinRecordKey, buildTopicPinPut, buildTopicPinTombstone, } from './TopicPinReplicatedStore.js';
|
|
26
|
+
/** Pin a topic to a machine: ONE HLC stamps both the journal PUT and the local set(). */
|
|
27
|
+
export function setPinWithOneHlc(deps, sessionKey, preferredMachine, pinned = true, pinnedBy) {
|
|
28
|
+
const topicNum = Number(sessionKey);
|
|
29
|
+
let minted;
|
|
30
|
+
if (deps.emitter && Number.isFinite(topicNum)) {
|
|
31
|
+
const recordKey = deriveTopicPinRecordKey(topicNum);
|
|
32
|
+
if (recordKey) {
|
|
33
|
+
const build = buildTopicPinPut(topicNum, preferredMachine, pinned);
|
|
34
|
+
deps.emitter.emit(TOPIC_PIN_STORE_KEY, recordKey, (hlc, origin, observed) => {
|
|
35
|
+
const data = build(hlc, origin, observed);
|
|
36
|
+
if (data !== null)
|
|
37
|
+
minted = hlc; // capture ONLY a stamp that actually rode a record
|
|
38
|
+
return data;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
deps.pinStore.set(sessionKey, preferredMachine, pinned, minted, pinnedBy);
|
|
43
|
+
return minted !== undefined ? { hlc: minted } : {};
|
|
44
|
+
}
|
|
45
|
+
/** Unpin a topic: the tombstone rides the SAME stamp the local clear is ordered by. */
|
|
46
|
+
export function clearPinWithTombstone(deps, sessionKey) {
|
|
47
|
+
const hadPin = deps.pinStore.get(sessionKey) !== null;
|
|
48
|
+
const topicNum = Number(sessionKey);
|
|
49
|
+
let minted;
|
|
50
|
+
if (deps.emitter && Number.isFinite(topicNum)) {
|
|
51
|
+
const recordKey = deriveTopicPinRecordKey(topicNum);
|
|
52
|
+
if (recordKey) {
|
|
53
|
+
const deletedAt = (deps.now ?? (() => new Date()))().toISOString();
|
|
54
|
+
const build = buildTopicPinTombstone(topicNum, deletedAt);
|
|
55
|
+
deps.emitter.emit(TOPIC_PIN_STORE_KEY, recordKey, (hlc, origin, observed) => {
|
|
56
|
+
const data = build(hlc, origin, observed);
|
|
57
|
+
if (data !== null)
|
|
58
|
+
minted = hlc;
|
|
59
|
+
return data;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
deps.pinStore.clear(sessionKey);
|
|
64
|
+
return minted !== undefined ? { hadPin, hlc: minted } : { hadPin };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=TopicPinMutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopicPinMutation.js","sourceRoot":"","sources":["../../src/core/TopicPinMutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,8BAA8B,CAAC;AAkBtC,yFAAyF;AACzF,MAAM,UAAU,gBAAgB,CAC9B,IAAqB,EACrB,UAAkB,EAClB,gBAAwB,EACxB,MAAM,GAAG,IAAI,EACb,QAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,MAAgC,CAAC;IACrC,IAAI,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;YACnE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;gBAC1E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;gBAC1C,IAAI,IAAI,KAAK,IAAI;oBAAE,MAAM,GAAG,GAAG,CAAC,CAAC,mDAAmD;gBACpF,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1E,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CACnC,IAAqB,EACrB,UAAkB;IAElB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,MAAgC,CAAC;IACrC,IAAI,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;YACnE,MAAM,KAAK,GAAG,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;gBAC1E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;gBAC1C,IAAI,IAAI,KAAK,IAAI;oBAAE,MAAM,GAAG,GAAG,CAAC;gBAChC,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAChC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;AACrE,CAAC"}
|
|
@@ -26,6 +26,9 @@ import type { HlcTimestamp } from './HybridLogicalClock.js';
|
|
|
26
26
|
export declare const TOPIC_PIN_RECORD_KIND = "topic-pin-record";
|
|
27
27
|
/** The store key used by the emitter dark-gate + the rollback-unmerge getByStore wiring. */
|
|
28
28
|
export declare const TOPIC_PIN_STORE_KEY = "topicPins";
|
|
29
|
+
/** The same clamp for consumers outside this module (the U4.1 fold view's second pass —
|
|
30
|
+
* ONE validation authority, never a re-implemented regex). */
|
|
31
|
+
export declare function isValidPinMachineId(id: unknown): id is string;
|
|
29
32
|
/** recordKey = the cross-machine IDENTITY SURFACE = the topic id as a string (a pin is
|
|
30
33
|
* per-topic; a re-pin to a different machine is the SAME key with a newer HLC). */
|
|
31
34
|
export declare function deriveTopicPinRecordKey(topic: number | string): string | null;
|