@amalgm/live 0.1.0
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/PURPOSE.md +103 -0
- package/README.md +45 -0
- package/dist/contracts/binding.d.ts +49 -0
- package/dist/contracts/binding.d.ts.map +1 -0
- package/dist/contracts/binding.js +23 -0
- package/dist/contracts/binding.js.map +1 -0
- package/dist/contracts/content.d.ts +42 -0
- package/dist/contracts/content.d.ts.map +1 -0
- package/dist/contracts/content.js +72 -0
- package/dist/contracts/content.js.map +1 -0
- package/dist/contracts/docs.d.ts +64 -0
- package/dist/contracts/docs.d.ts.map +1 -0
- package/dist/contracts/docs.js +51 -0
- package/dist/contracts/docs.js.map +1 -0
- package/dist/contracts/events.d.ts +45 -0
- package/dist/contracts/events.d.ts.map +1 -0
- package/dist/contracts/events.js +38 -0
- package/dist/contracts/events.js.map +1 -0
- package/dist/contracts/frames/gateway.d.ts +83 -0
- package/dist/contracts/frames/gateway.d.ts.map +1 -0
- package/dist/contracts/frames/gateway.js +35 -0
- package/dist/contracts/frames/gateway.js.map +1 -0
- package/dist/contracts/frames/stream.d.ts +59 -0
- package/dist/contracts/frames/stream.d.ts.map +1 -0
- package/dist/contracts/frames/stream.js +17 -0
- package/dist/contracts/frames/stream.js.map +1 -0
- package/dist/contracts/frames/tunnel.d.ts +78 -0
- package/dist/contracts/frames/tunnel.d.ts.map +1 -0
- package/dist/contracts/frames/tunnel.js +55 -0
- package/dist/contracts/frames/tunnel.js.map +1 -0
- package/dist/contracts/index.d.ts +19 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +19 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/mutation.d.ts +51 -0
- package/dist/contracts/mutation.d.ts.map +1 -0
- package/dist/contracts/mutation.js +55 -0
- package/dist/contracts/mutation.js.map +1 -0
- package/dist/contracts/presence.d.ts +46 -0
- package/dist/contracts/presence.d.ts.map +1 -0
- package/dist/contracts/presence.js +34 -0
- package/dist/contracts/presence.js.map +1 -0
- package/dist/contracts/resources.d.ts +52 -0
- package/dist/contracts/resources.d.ts.map +1 -0
- package/dist/contracts/resources.js +105 -0
- package/dist/contracts/resources.js.map +1 -0
- package/dist/contracts/snapshot.d.ts +24 -0
- package/dist/contracts/snapshot.d.ts.map +1 -0
- package/dist/contracts/snapshot.js +28 -0
- package/dist/contracts/snapshot.js.map +1 -0
- package/dist/contracts/validate.d.ts +32 -0
- package/dist/contracts/validate.d.ts.map +1 -0
- package/dist/contracts/validate.js +25 -0
- package/dist/contracts/validate.js.map +1 -0
- package/dist/detection/adapter-contract.d.ts +134 -0
- package/dist/detection/adapter-contract.d.ts.map +1 -0
- package/dist/detection/adapter-contract.js +162 -0
- package/dist/detection/adapter-contract.js.map +1 -0
- package/dist/detection/classify.d.ts +31 -0
- package/dist/detection/classify.d.ts.map +1 -0
- package/dist/detection/classify.js +28 -0
- package/dist/detection/classify.js.map +1 -0
- package/dist/detection/continuity.d.ts +54 -0
- package/dist/detection/continuity.d.ts.map +1 -0
- package/dist/detection/continuity.js +85 -0
- package/dist/detection/continuity.js.map +1 -0
- package/dist/detection/coverage.d.ts +43 -0
- package/dist/detection/coverage.d.ts.map +1 -0
- package/dist/detection/coverage.js +60 -0
- package/dist/detection/coverage.js.map +1 -0
- package/dist/detection/diff.d.ts +144 -0
- package/dist/detection/diff.d.ts.map +1 -0
- package/dist/detection/diff.js +187 -0
- package/dist/detection/diff.js.map +1 -0
- package/dist/detection/enrollment.d.ts +16 -0
- package/dist/detection/enrollment.d.ts.map +1 -0
- package/dist/detection/enrollment.js +70 -0
- package/dist/detection/enrollment.js.map +1 -0
- package/dist/detection/index.d.ts +29 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +29 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/detection/refusal.d.ts +33 -0
- package/dist/detection/refusal.d.ts.map +1 -0
- package/dist/detection/refusal.js +40 -0
- package/dist/detection/refusal.js.map +1 -0
- package/dist/entities/bytes.d.ts +18 -0
- package/dist/entities/bytes.d.ts.map +1 -0
- package/dist/entities/bytes.js +61 -0
- package/dist/entities/bytes.js.map +1 -0
- package/dist/entities/cloud.d.ts +82 -0
- package/dist/entities/cloud.d.ts.map +1 -0
- package/dist/entities/cloud.js +136 -0
- package/dist/entities/cloud.js.map +1 -0
- package/dist/entities/index.d.ts +23 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +23 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/moves.d.ts +42 -0
- package/dist/entities/moves.d.ts.map +1 -0
- package/dist/entities/moves.js +82 -0
- package/dist/entities/moves.js.map +1 -0
- package/dist/entities/repo-states.d.ts +121 -0
- package/dist/entities/repo-states.d.ts.map +1 -0
- package/dist/entities/repo-states.js +127 -0
- package/dist/entities/repo-states.js.map +1 -0
- package/dist/entities/rules.d.ts +104 -0
- package/dist/entities/rules.d.ts.map +1 -0
- package/dist/entities/rules.js +200 -0
- package/dist/entities/rules.js.map +1 -0
- package/dist/entities/types.d.ts +94 -0
- package/dist/entities/types.d.ts.map +1 -0
- package/dist/entities/types.js +40 -0
- package/dist/entities/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/machines/arbitration.d.ts +36 -0
- package/dist/machines/arbitration.d.ts.map +1 -0
- package/dist/machines/arbitration.js +37 -0
- package/dist/machines/arbitration.js.map +1 -0
- package/dist/machines/backoff.d.ts +36 -0
- package/dist/machines/backoff.d.ts.map +1 -0
- package/dist/machines/backoff.js +34 -0
- package/dist/machines/backoff.js.map +1 -0
- package/dist/machines/bootstrap.d.ts +65 -0
- package/dist/machines/bootstrap.d.ts.map +1 -0
- package/dist/machines/bootstrap.js +105 -0
- package/dist/machines/bootstrap.js.map +1 -0
- package/dist/machines/chunks.d.ts +51 -0
- package/dist/machines/chunks.d.ts.map +1 -0
- package/dist/machines/chunks.js +67 -0
- package/dist/machines/chunks.js.map +1 -0
- package/dist/machines/index.d.ts +14 -0
- package/dist/machines/index.d.ts.map +1 -0
- package/dist/machines/index.js +14 -0
- package/dist/machines/index.js.map +1 -0
- package/dist/machines/journal.d.ts +63 -0
- package/dist/machines/journal.d.ts.map +1 -0
- package/dist/machines/journal.js +71 -0
- package/dist/machines/journal.js.map +1 -0
- package/dist/machines/ordering.d.ts +53 -0
- package/dist/machines/ordering.d.ts.map +1 -0
- package/dist/machines/ordering.js +87 -0
- package/dist/machines/ordering.js.map +1 -0
- package/dist/machines/presence.d.ts +26 -0
- package/dist/machines/presence.d.ts.map +1 -0
- package/dist/machines/presence.js +49 -0
- package/dist/machines/presence.js.map +1 -0
- package/dist/merge/index.d.ts +9 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +9 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/merge3.d.ts +39 -0
- package/dist/merge/merge3.d.ts.map +1 -0
- package/dist/merge/merge3.js +188 -0
- package/dist/merge/merge3.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/detection/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,WAAW,CAAC;AAEnD,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,gBAAgB,GAAG,QAAQ,CAEnE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amalgm's file-type policy: which physical type a piece of file ground
|
|
3
|
+
* takes. Ratified by entity-model.md ("Formats live underneath the file
|
|
4
|
+
* types"; text versus binary is an adapter difference, never an
|
|
5
|
+
* identity difference).
|
|
6
|
+
*
|
|
7
|
+
* Classification is an INJECTED policy everywhere it is used — the
|
|
8
|
+
* registration boundary and its lenses refuse to construct without one
|
|
9
|
+
* — so it lives in its own file with no other job: one definition,
|
|
10
|
+
* replaceable whole.
|
|
11
|
+
*
|
|
12
|
+
* The rule is the oldest one there is: bytes holding a NUL are not
|
|
13
|
+
* text. What matters is WHERE that fact comes from. It is not read
|
|
14
|
+
* here. Detect's single read of a file produces its fingerprint and
|
|
15
|
+
* this report together, and this policy only names the type that report
|
|
16
|
+
* implies.
|
|
17
|
+
*
|
|
18
|
+
* That is the whole law: A FILE'S HASH AND ITS TYPE DESCRIBE THE SAME
|
|
19
|
+
* OBSERVED BYTES. A policy that opened the ground itself would be a
|
|
20
|
+
* second look — and a second look can meet different bytes, or none at
|
|
21
|
+
* all, leaving a record whose type contradicts the payload beside it.
|
|
22
|
+
* So this function takes evidence, never a path; it is total, and it
|
|
23
|
+
* cannot fail.
|
|
24
|
+
*/
|
|
25
|
+
export function classifyFile({ binary }) {
|
|
26
|
+
return binary ? 'file.binary' : 'file.text';
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/detection/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,MAAM,UAAU,YAAY,CAAC,EAAE,MAAM,EAAoB;IACvD,OAAO,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root continuity verdicts: what a root's WITNESSED physical identity
|
|
3
|
+
* is, whether its recorded address still holds its ground, and whether
|
|
4
|
+
* a piece of ground rescues a moved root. Ratified by entity-model.md
|
|
5
|
+
* ("Ground that moves is rescued by continuity" — the directory law
|
|
6
|
+
* lifted to roots; "the follow rides witnessed continuity, never
|
|
7
|
+
* clairvoyance").
|
|
8
|
+
*
|
|
9
|
+
* Pure verdicts over injected answers — the host supplies the one look
|
|
10
|
+
* at an address (`statAt`) and the store's witnessed channel row
|
|
11
|
+
* (`channelIdentity`); the ACTIONS a verdict triggers (rebind, presence
|
|
12
|
+
* transitions, the undecided-address decision) stay in the host's
|
|
13
|
+
* wiring, because they re-aim watchers and scans.
|
|
14
|
+
*/
|
|
15
|
+
import type { PhysicalIdentity } from './diff.js';
|
|
16
|
+
import type { RootKind } from './coverage.js';
|
|
17
|
+
/** What one look at an address found standing there. */
|
|
18
|
+
export type GroundKind = 'file' | 'dir' | 'link' | 'other';
|
|
19
|
+
export interface GroundStat extends PhysicalIdentity {
|
|
20
|
+
readonly kind: GroundKind;
|
|
21
|
+
}
|
|
22
|
+
export interface ContinuityRoot {
|
|
23
|
+
readonly rootId: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly kind: RootKind;
|
|
26
|
+
/** Directory roots: the root's own witnessed identity; null/absent when unwitnessed. */
|
|
27
|
+
readonly device?: number | null;
|
|
28
|
+
readonly inode?: number | null;
|
|
29
|
+
}
|
|
30
|
+
export interface ContinuityPorts {
|
|
31
|
+
/**
|
|
32
|
+
* One look at an address: the ground's physical identity and kind, or
|
|
33
|
+
* null when the look did not return ground. ANY failed look answers
|
|
34
|
+
* null here — at the evidence horizon's edge, "could not look" and
|
|
35
|
+
* "not there" both leave the address undecided, so this seam never
|
|
36
|
+
* needs the absent/unsettled split.
|
|
37
|
+
*/
|
|
38
|
+
readonly statAt: (path: string) => GroundStat | null;
|
|
39
|
+
/**
|
|
40
|
+
* A single-ground root's one channel row identity — current as of the
|
|
41
|
+
* last completed scan — or null when unwitnessed. The file laws
|
|
42
|
+
* absorb atomic-save and retarget churn each time a scan witnesses
|
|
43
|
+
* the ground, and continuity can only ever claim what a scan has
|
|
44
|
+
* witnessed.
|
|
45
|
+
*/
|
|
46
|
+
readonly channelIdentity: (rootId: string) => PhysicalIdentity | null;
|
|
47
|
+
}
|
|
48
|
+
export interface ContinuityVerdicts {
|
|
49
|
+
readonly rootIdentity: (root: ContinuityRoot) => PhysicalIdentity | null;
|
|
50
|
+
readonly findMovedRoot: (stat: GroundStat, roots: readonly ContinuityRoot[]) => ContinuityRoot | null;
|
|
51
|
+
readonly addressUndecided: (root: ContinuityRoot) => boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare function createContinuity({ statAt, channelIdentity }: ContinuityPorts): ContinuityVerdicts;
|
|
54
|
+
//# sourceMappingURL=continuity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuity.d.ts","sourceRoot":"","sources":["../../src/detection/continuity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,UAAW,SAAQ,gBAAgB;IAClD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;IACrD;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;CACvE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,gBAAgB,GAAG,IAAI,CAAC;IACzE,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,KAAK,cAAc,GAAG,IAAI,CAAC;IACtG,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC;CAC9D;AAED,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,eAAe,GAAG,kBAAkB,CAmEjG"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root continuity verdicts: what a root's WITNESSED physical identity
|
|
3
|
+
* is, whether its recorded address still holds its ground, and whether
|
|
4
|
+
* a piece of ground rescues a moved root. Ratified by entity-model.md
|
|
5
|
+
* ("Ground that moves is rescued by continuity" — the directory law
|
|
6
|
+
* lifted to roots; "the follow rides witnessed continuity, never
|
|
7
|
+
* clairvoyance").
|
|
8
|
+
*
|
|
9
|
+
* Pure verdicts over injected answers — the host supplies the one look
|
|
10
|
+
* at an address (`statAt`) and the store's witnessed channel row
|
|
11
|
+
* (`channelIdentity`); the ACTIONS a verdict triggers (rebind, presence
|
|
12
|
+
* transitions, the undecided-address decision) stay in the host's
|
|
13
|
+
* wiring, because they re-aim watchers and scans.
|
|
14
|
+
*/
|
|
15
|
+
export function createContinuity({ statAt, channelIdentity }) {
|
|
16
|
+
/**
|
|
17
|
+
* A root's WITNESSED physical identity, for continuity rescue.
|
|
18
|
+
* Directory roots carry their own device:inode (nothing replaces a
|
|
19
|
+
* directory's inode in place). A single-ground root's identity lives
|
|
20
|
+
* in its one channel row. Ground that changed inodes inside a
|
|
21
|
+
* still-open settle window is unwitnessed — no evidence, no claim.
|
|
22
|
+
*/
|
|
23
|
+
function rootIdentity(root) {
|
|
24
|
+
if (root.kind !== 'file') {
|
|
25
|
+
const { device, inode } = root;
|
|
26
|
+
return device == null || inode == null ? null : { device, inode };
|
|
27
|
+
}
|
|
28
|
+
return channelIdentity(root.rootId);
|
|
29
|
+
}
|
|
30
|
+
/** Orphaned means the recorded address no longer holds this ground. */
|
|
31
|
+
function orphanedAtAddress(root) {
|
|
32
|
+
const identity = rootIdentity(root);
|
|
33
|
+
if (!identity)
|
|
34
|
+
return false;
|
|
35
|
+
const stat = statAt(root.path);
|
|
36
|
+
if (!stat)
|
|
37
|
+
return true;
|
|
38
|
+
return stat.device !== identity.device || stat.inode !== identity.inode;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Continuity claims a root exactly like it claims the directories
|
|
42
|
+
* inside one: unique device:inode match, exactly one candidate, and
|
|
43
|
+
* only for a path-orphan — a root whose recorded address still holds
|
|
44
|
+
* its ground is never stolen by a lookalike.
|
|
45
|
+
*/
|
|
46
|
+
function findMovedRoot(stat, roots) {
|
|
47
|
+
const wantDir = stat.kind === 'dir';
|
|
48
|
+
const matches = roots.filter((root) => {
|
|
49
|
+
if (wantDir !== (root.kind !== 'file'))
|
|
50
|
+
return false;
|
|
51
|
+
const identity = rootIdentity(root);
|
|
52
|
+
return identity !== null && identity.device === stat.device && identity.inode === stat.inode;
|
|
53
|
+
});
|
|
54
|
+
const only = matches.length === 1 ? matches[0] : null;
|
|
55
|
+
return only && orphanedAtAddress(only) ? only : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A scan's evidence horizon is the root's ground; the root's own
|
|
59
|
+
* address is the horizon's edge. Ground missing there — or a
|
|
60
|
+
* directory root's address wearing a different physical identity — is
|
|
61
|
+
* UNDECIDABLE from here: moved away, deleted, or replaced by a
|
|
62
|
+
* lookalike all present the same address, and concluding "deleted"
|
|
63
|
+
* would end every channel a rescue could have saved. So the scan
|
|
64
|
+
* concludes nothing: beliefs freeze, the referee convicts the
|
|
65
|
+
* divergence, and the next enrollment moment decides — naming the new
|
|
66
|
+
* address rescues, re-naming this one adopts, and explicit removal is
|
|
67
|
+
* the only deletion (an external filesystem change is never
|
|
68
|
+
* permission to end identity). A single-ground root's address
|
|
69
|
+
* lawfully churns inodes (atomic saves, retargets), so only absence —
|
|
70
|
+
* or foreign directory ground — freezes it; presence is the file
|
|
71
|
+
* laws' business.
|
|
72
|
+
*/
|
|
73
|
+
function addressUndecided(root) {
|
|
74
|
+
const stat = statAt(root.path);
|
|
75
|
+
if (!stat)
|
|
76
|
+
return true;
|
|
77
|
+
if (root.kind === 'file')
|
|
78
|
+
return stat.kind !== 'file' && stat.kind !== 'link';
|
|
79
|
+
if (stat.kind !== 'dir')
|
|
80
|
+
return true;
|
|
81
|
+
return root.device != null && (stat.device !== root.device || stat.inode !== root.inode);
|
|
82
|
+
}
|
|
83
|
+
return { rootIdentity, findMovedRoot, addressUndecided };
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=continuity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuity.js","sourceRoot":"","sources":["../../src/detection/continuity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA8CH,MAAM,UAAU,gBAAgB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAmB;IAC3E;;;;;;OAMG;IACH,SAAS,YAAY,CAAC,IAAoB;QACxC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;YAC/B,OAAO,MAAM,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACpE,CAAC;QACD,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,uEAAuE;IACvE,SAAS,iBAAiB,CAAC,IAAoB;QAC7C,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,KAAK,CAAC;IAC1E,CAAC;IAED;;;;;OAKG;IACH,SAAS,aAAa,CAAC,IAAgB,EAAE,KAAgC;QACvE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YACpC,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC;YACrD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC;QAC/F,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACvD,OAAO,IAAI,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,SAAS,gBAAgB,CAAC,IAAoB;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;QAC9E,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3F,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coverage law: one OS watcher per outermost tree. Ratified by
|
|
3
|
+
* entity-model.md (enclosed-ground coverage) and
|
|
4
|
+
* entity-implementation.md (the doorbell matrix).
|
|
5
|
+
*
|
|
6
|
+
* A recursive watcher on a directory hears every event beneath it, at
|
|
7
|
+
* any depth — so a root enclosed by watched ground already has its
|
|
8
|
+
* content observed, exactly as its ADDRESS is already observed. Opening
|
|
9
|
+
* a second watcher over enclosed ground buys nothing on any platform
|
|
10
|
+
* and, on Linux — where a recursive watch is silently one inotify slot
|
|
11
|
+
* PER DIRECTORY against a small kernel budget — exhausts the kernel.
|
|
12
|
+
* Doorbells are shared; what a root DOES with its dings (files
|
|
13
|
+
* channels, repo Card+Checkpoint) stays its own type's business.
|
|
14
|
+
*
|
|
15
|
+
* Pure functions over root rows — no handles, no state. The host owns
|
|
16
|
+
* opening and closing; this module only answers "who guards whom".
|
|
17
|
+
* Addresses are absolute '/'-separated paths with no trailing
|
|
18
|
+
* separator, as the enrollment boundary records them.
|
|
19
|
+
*/
|
|
20
|
+
/** The three root kinds; only 'file' changes a doorbell's ground. */
|
|
21
|
+
export type RootKind = 'file' | 'files' | 'repo';
|
|
22
|
+
export interface WatchRoot {
|
|
23
|
+
readonly rootId: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly kind: RootKind;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The ground a root's doorbell must cover: a directory root guards
|
|
29
|
+
* itself; a single-file root's bell lives on its parent directory (the
|
|
30
|
+
* file's own inode goes deaf on atomic saves).
|
|
31
|
+
*/
|
|
32
|
+
export declare function doorbellGround(root: WatchRoot): string;
|
|
33
|
+
/**
|
|
34
|
+
* The root whose OS watcher covers `root`'s doorbell ground, or null
|
|
35
|
+
* when `root` guards itself. The OUTERMOST enclosing directory root
|
|
36
|
+
* wins: with A ⊃ B ⊃ C only A holds a handle, and every deeper root —
|
|
37
|
+
* B, C, nested repos, single files — rides it. A file root can never
|
|
38
|
+
* cover anything.
|
|
39
|
+
*/
|
|
40
|
+
export declare function watchOwnerOf(root: WatchRoot, roots: readonly WatchRoot[]): WatchRoot | null;
|
|
41
|
+
/** Every live root covered by `owner` (owner excluded). */
|
|
42
|
+
export declare function coveredRoots(owner: WatchRoot, roots: readonly WatchRoot[]): WatchRoot[];
|
|
43
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/detection/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AASD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,IAAI,CAS3F;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAGvF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coverage law: one OS watcher per outermost tree. Ratified by
|
|
3
|
+
* entity-model.md (enclosed-ground coverage) and
|
|
4
|
+
* entity-implementation.md (the doorbell matrix).
|
|
5
|
+
*
|
|
6
|
+
* A recursive watcher on a directory hears every event beneath it, at
|
|
7
|
+
* any depth — so a root enclosed by watched ground already has its
|
|
8
|
+
* content observed, exactly as its ADDRESS is already observed. Opening
|
|
9
|
+
* a second watcher over enclosed ground buys nothing on any platform
|
|
10
|
+
* and, on Linux — where a recursive watch is silently one inotify slot
|
|
11
|
+
* PER DIRECTORY against a small kernel budget — exhausts the kernel.
|
|
12
|
+
* Doorbells are shared; what a root DOES with its dings (files
|
|
13
|
+
* channels, repo Card+Checkpoint) stays its own type's business.
|
|
14
|
+
*
|
|
15
|
+
* Pure functions over root rows — no handles, no state. The host owns
|
|
16
|
+
* opening and closing; this module only answers "who guards whom".
|
|
17
|
+
* Addresses are absolute '/'-separated paths with no trailing
|
|
18
|
+
* separator, as the enrollment boundary records them.
|
|
19
|
+
*/
|
|
20
|
+
/** '/a/b' → '/a'; '/a' → '/'; a bare name → '.'. */
|
|
21
|
+
const parentDirOf = (path) => {
|
|
22
|
+
const cut = path.lastIndexOf('/');
|
|
23
|
+
if (cut < 0)
|
|
24
|
+
return '.';
|
|
25
|
+
return cut === 0 ? '/' : path.slice(0, cut);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The ground a root's doorbell must cover: a directory root guards
|
|
29
|
+
* itself; a single-file root's bell lives on its parent directory (the
|
|
30
|
+
* file's own inode goes deaf on atomic saves).
|
|
31
|
+
*/
|
|
32
|
+
export function doorbellGround(root) {
|
|
33
|
+
return root.kind === 'file' ? parentDirOf(root.path) : root.path;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The root whose OS watcher covers `root`'s doorbell ground, or null
|
|
37
|
+
* when `root` guards itself. The OUTERMOST enclosing directory root
|
|
38
|
+
* wins: with A ⊃ B ⊃ C only A holds a handle, and every deeper root —
|
|
39
|
+
* B, C, nested repos, single files — rides it. A file root can never
|
|
40
|
+
* cover anything.
|
|
41
|
+
*/
|
|
42
|
+
export function watchOwnerOf(root, roots) {
|
|
43
|
+
const ground = doorbellGround(root);
|
|
44
|
+
let owner = null;
|
|
45
|
+
for (const other of roots) {
|
|
46
|
+
if (other.rootId === root.rootId || other.kind === 'file')
|
|
47
|
+
continue;
|
|
48
|
+
if (ground !== other.path && !ground.startsWith(`${other.path}/`))
|
|
49
|
+
continue;
|
|
50
|
+
if (!owner || other.path.length < owner.path.length)
|
|
51
|
+
owner = other;
|
|
52
|
+
}
|
|
53
|
+
return owner;
|
|
54
|
+
}
|
|
55
|
+
/** Every live root covered by `owner` (owner excluded). */
|
|
56
|
+
export function coveredRoots(owner, roots) {
|
|
57
|
+
return roots.filter((root) => root.rootId !== owner.rootId
|
|
58
|
+
&& watchOwnerOf(root, roots)?.rootId === owner.rootId);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/detection/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAWH,oDAAoD;AACpD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IACxB,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC9C,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AACnE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,KAA2B;IACvE,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,KAAK,GAAqB,IAAI,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QACpE,IAAI,MAAM,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC;YAAE,SAAS;QAC5E,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM;YAAE,KAAK,GAAG,KAAK,CAAC;IACrE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAAC,KAAgB,EAAE,KAA2B;IACxE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;WACrD,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compare core of detection: rows (store memory) + entries (disk
|
|
3
|
+
* truth) in, actions out. Pure — the walk that produces entries and the
|
|
4
|
+
* hashing that settles `checks` are the host's; only the identity
|
|
5
|
+
* verdicts live here. Ratified by entity-model.md ("Address and
|
|
6
|
+
* physical continuity are the two identity witnesses, never content").
|
|
7
|
+
*
|
|
8
|
+
* Three kinds of channel, three orderings of the same two witnesses:
|
|
9
|
+
*
|
|
10
|
+
* - Files: path claims first; the inode rescues path-orphans. An
|
|
11
|
+
* atomic save replaces a file's inode in place, so the address must
|
|
12
|
+
* absorb that churn.
|
|
13
|
+
* - Directories: physical continuity claims first; the address claims
|
|
14
|
+
* what no inode claims. Nothing replaces a directory's inode in
|
|
15
|
+
* place, so a known inode at a new address IS the directory, moved.
|
|
16
|
+
* - Links: the FILE rules, for the file reason — retarget is unlink +
|
|
17
|
+
* recreate, so the inode churns exactly like an atomic save.
|
|
18
|
+
*/
|
|
19
|
+
/** One piece of ground's physical identity: device:inode. */
|
|
20
|
+
export interface PhysicalIdentity {
|
|
21
|
+
readonly device: number;
|
|
22
|
+
readonly inode: number;
|
|
23
|
+
}
|
|
24
|
+
/** A row and an entry the compare has bound to one channel. */
|
|
25
|
+
export interface Match<Row, Entry> {
|
|
26
|
+
readonly row: Row;
|
|
27
|
+
readonly entry: Entry;
|
|
28
|
+
}
|
|
29
|
+
/** Disk truth for one file, as the walk reported it. */
|
|
30
|
+
export interface FileEntry extends PhysicalIdentity {
|
|
31
|
+
readonly relPath: string;
|
|
32
|
+
readonly size: number;
|
|
33
|
+
readonly mtimeMs: number;
|
|
34
|
+
}
|
|
35
|
+
/** Store memory for one file channel. `hashedAtMs` is the instant the bytes were last read. */
|
|
36
|
+
export interface FileRow extends PhysicalIdentity {
|
|
37
|
+
readonly fileId: string;
|
|
38
|
+
readonly relPath: string;
|
|
39
|
+
readonly size: number;
|
|
40
|
+
readonly mtimeMs: number;
|
|
41
|
+
readonly hashedAtMs?: number | null;
|
|
42
|
+
}
|
|
43
|
+
export interface DirEntry extends PhysicalIdentity {
|
|
44
|
+
readonly relPath: string;
|
|
45
|
+
}
|
|
46
|
+
export interface DirRow extends PhysicalIdentity {
|
|
47
|
+
readonly dirId: string;
|
|
48
|
+
readonly relPath: string;
|
|
49
|
+
}
|
|
50
|
+
/** Disk truth for one symlink: the written target text, read at walk time, never followed. */
|
|
51
|
+
export interface LinkEntry extends PhysicalIdentity {
|
|
52
|
+
readonly relPath: string;
|
|
53
|
+
readonly target: string;
|
|
54
|
+
}
|
|
55
|
+
export interface LinkRow extends PhysicalIdentity {
|
|
56
|
+
readonly linkId: string;
|
|
57
|
+
readonly relPath: string;
|
|
58
|
+
readonly target: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Physical continuity, shared by every compare function: a row and an
|
|
62
|
+
* entry sharing one device:inode, exactly one match on both sides → the
|
|
63
|
+
* same physical thing. Anything else is never guessed. For files this
|
|
64
|
+
* rescues path-orphans (rule 2); for directories it IS rule 1.
|
|
65
|
+
*/
|
|
66
|
+
export declare function matchByInode<Row extends PhysicalIdentity, Entry extends PhysicalIdentity>(rows: readonly Row[], entries: readonly Entry[]): Array<Match<Row, Entry>>;
|
|
67
|
+
export interface DirDiff {
|
|
68
|
+
readonly renames: Array<Match<DirRow, DirEntry>>;
|
|
69
|
+
readonly retags: Array<Match<DirRow, DirEntry>>;
|
|
70
|
+
readonly creates: DirEntry[];
|
|
71
|
+
readonly deletes: DirRow[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The compare function for directory channels — the file identity rules
|
|
75
|
+
* in the OPPOSITE order, for the mirror reason. A file's inode is
|
|
76
|
+
* replaced in place on every atomic save, so for files the path must
|
|
77
|
+
* absorb that churn (path claims first). Nothing replaces a directory's
|
|
78
|
+
* inode in place: a known inode at a new address IS the directory,
|
|
79
|
+
* moved — even when its old address was reoccupied in the same interval
|
|
80
|
+
* (two directories exchanging names cross by inode, exactly like the
|
|
81
|
+
* files inside them). So:
|
|
82
|
+
* 1. Physical continuity claims the channel: unique device:inode,
|
|
83
|
+
* whole sets, same path = continued, new path = rename.
|
|
84
|
+
* 2. The address claims what no inode claims — recreation under an old
|
|
85
|
+
* name is the same channel wearing a never-seen inode (`retags`,
|
|
86
|
+
* silent).
|
|
87
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
88
|
+
* No content: a directory's membership is derived state upstream, so
|
|
89
|
+
* there is nothing to hash or check.
|
|
90
|
+
*/
|
|
91
|
+
export declare function diffDirState(rows: readonly DirRow[], entries: readonly DirEntry[]): DirDiff;
|
|
92
|
+
export interface LinkDiff {
|
|
93
|
+
readonly changes: Array<Match<LinkRow, LinkEntry>>;
|
|
94
|
+
readonly retags: Array<Match<LinkRow, LinkEntry>>;
|
|
95
|
+
readonly renames: Array<Match<LinkRow, LinkEntry>>;
|
|
96
|
+
readonly creates: LinkEntry[];
|
|
97
|
+
readonly deletes: LinkRow[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The compare function for link channels — the FILE identity rules, for
|
|
101
|
+
* the file reason: a symlink cannot be retargeted in place (retarget is
|
|
102
|
+
* unlink + recreate, so the inode churns exactly like an atomic save),
|
|
103
|
+
* so the path absorbs that churn and claims first; the inode rescues
|
|
104
|
+
* path-orphans (`mv` preserves a symlink's inode). The payload is the
|
|
105
|
+
* target string, already read at walk time. A consequence worth naming:
|
|
106
|
+
* two links exchanging names read as two retargets (path-first),
|
|
107
|
+
* because a link's meaning IS its written address.
|
|
108
|
+
* 1. Path claims the channel: same target = continued (a new inode
|
|
109
|
+
* under an old name with an old target is silent recreation),
|
|
110
|
+
* different target = changed.
|
|
111
|
+
* 2. Inode rescues path-orphans: renames.
|
|
112
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
113
|
+
*/
|
|
114
|
+
export declare function diffLinkState(rows: readonly LinkRow[], entries: readonly LinkEntry[]): LinkDiff;
|
|
115
|
+
export interface RootDiff {
|
|
116
|
+
/** Files whose content must be read before an event may be claimed. */
|
|
117
|
+
readonly checks: Array<Match<FileRow, FileEntry>>;
|
|
118
|
+
readonly renames: Array<Match<FileRow, FileEntry>>;
|
|
119
|
+
readonly creates: FileEntry[];
|
|
120
|
+
readonly deletes: FileRow[];
|
|
121
|
+
readonly unchanged: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The compare function for file channels — doorbells and rescans both
|
|
125
|
+
* end here. Hashing is deferred — `checks` name the files whose content
|
|
126
|
+
* must be read before an event may be claimed.
|
|
127
|
+
*
|
|
128
|
+
* Identity rules, in order:
|
|
129
|
+
* 1. Path claims the channel. Same rel_path = same file_id, even when
|
|
130
|
+
* the inode changed (atomic saves replace the inode on every
|
|
131
|
+
* write).
|
|
132
|
+
* 2. Inode rescues path-orphans (matchByInode above).
|
|
133
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
134
|
+
*
|
|
135
|
+
* The single stat-shortcut this layer allows itself: a (size, mtime,
|
|
136
|
+
* inode) triple identical to the stored one is skipped as unchanged —
|
|
137
|
+
* but only when the mtime is strictly older than the moment we last
|
|
138
|
+
* read the bytes (git's "racily clean" rule), and only when nothing
|
|
139
|
+
* pointed at the file. A path named in `mustCheck` (the doorbell said
|
|
140
|
+
* so) is always hashed: timestamps can lie, a delivered watcher event
|
|
141
|
+
* cannot be un-delivered.
|
|
142
|
+
*/
|
|
143
|
+
export declare function diffRootState(rows: readonly FileRow[], entries: readonly FileEntry[], mustCheck?: ReadonlySet<string> | null): RootDiff;
|
|
144
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/detection/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compare core of detection: rows (store memory) + entries (disk
|
|
3
|
+
* truth) in, actions out. Pure — the walk that produces entries and the
|
|
4
|
+
* hashing that settles `checks` are the host's; only the identity
|
|
5
|
+
* verdicts live here. Ratified by entity-model.md ("Address and
|
|
6
|
+
* physical continuity are the two identity witnesses, never content").
|
|
7
|
+
*
|
|
8
|
+
* Three kinds of channel, three orderings of the same two witnesses:
|
|
9
|
+
*
|
|
10
|
+
* - Files: path claims first; the inode rescues path-orphans. An
|
|
11
|
+
* atomic save replaces a file's inode in place, so the address must
|
|
12
|
+
* absorb that churn.
|
|
13
|
+
* - Directories: physical continuity claims first; the address claims
|
|
14
|
+
* what no inode claims. Nothing replaces a directory's inode in
|
|
15
|
+
* place, so a known inode at a new address IS the directory, moved.
|
|
16
|
+
* - Links: the FILE rules, for the file reason — retarget is unlink +
|
|
17
|
+
* recreate, so the inode churns exactly like an atomic save.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Physical continuity, shared by every compare function: a row and an
|
|
21
|
+
* entry sharing one device:inode, exactly one match on both sides → the
|
|
22
|
+
* same physical thing. Anything else is never guessed. For files this
|
|
23
|
+
* rescues path-orphans (rule 2); for directories it IS rule 1.
|
|
24
|
+
*/
|
|
25
|
+
export function matchByInode(rows, entries) {
|
|
26
|
+
const one = (map, key, value) => map.set(key, map.has(key) ? 'many' : value);
|
|
27
|
+
const rowsByInode = new Map();
|
|
28
|
+
for (const row of rows)
|
|
29
|
+
one(rowsByInode, `${row.device}:${row.inode}`, row);
|
|
30
|
+
const entriesByInode = new Map();
|
|
31
|
+
for (const entry of entries)
|
|
32
|
+
one(entriesByInode, `${entry.device}:${entry.inode}`, entry);
|
|
33
|
+
const matched = [];
|
|
34
|
+
for (const [key, entry] of entriesByInode) {
|
|
35
|
+
const row = rowsByInode.get(key);
|
|
36
|
+
if (entry === 'many' || !row || row === 'many')
|
|
37
|
+
continue;
|
|
38
|
+
matched.push({ row, entry });
|
|
39
|
+
}
|
|
40
|
+
return matched;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The compare function for directory channels — the file identity rules
|
|
44
|
+
* in the OPPOSITE order, for the mirror reason. A file's inode is
|
|
45
|
+
* replaced in place on every atomic save, so for files the path must
|
|
46
|
+
* absorb that churn (path claims first). Nothing replaces a directory's
|
|
47
|
+
* inode in place: a known inode at a new address IS the directory,
|
|
48
|
+
* moved — even when its old address was reoccupied in the same interval
|
|
49
|
+
* (two directories exchanging names cross by inode, exactly like the
|
|
50
|
+
* files inside them). So:
|
|
51
|
+
* 1. Physical continuity claims the channel: unique device:inode,
|
|
52
|
+
* whole sets, same path = continued, new path = rename.
|
|
53
|
+
* 2. The address claims what no inode claims — recreation under an old
|
|
54
|
+
* name is the same channel wearing a never-seen inode (`retags`,
|
|
55
|
+
* silent).
|
|
56
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
57
|
+
* No content: a directory's membership is derived state upstream, so
|
|
58
|
+
* there is nothing to hash or check.
|
|
59
|
+
*/
|
|
60
|
+
export function diffDirState(rows, entries) {
|
|
61
|
+
const matched = matchByInode(rows, entries);
|
|
62
|
+
const matchedRowIds = new Set(matched.map((pair) => pair.row.dirId));
|
|
63
|
+
const matchedEntries = new Set(matched.map((pair) => pair.entry));
|
|
64
|
+
const rowsByPath = new Map(rows.filter((row) => !matchedRowIds.has(row.dirId))
|
|
65
|
+
.map((row) => [row.relPath, row]));
|
|
66
|
+
const retags = [];
|
|
67
|
+
const creates = [];
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (matchedEntries.has(entry))
|
|
70
|
+
continue;
|
|
71
|
+
const row = rowsByPath.get(entry.relPath);
|
|
72
|
+
if (row) {
|
|
73
|
+
rowsByPath.delete(entry.relPath);
|
|
74
|
+
retags.push({ row, entry });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
creates.push(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
renames: matched.filter((pair) => pair.row.relPath !== pair.entry.relPath),
|
|
82
|
+
retags,
|
|
83
|
+
creates,
|
|
84
|
+
deletes: [...rowsByPath.values()],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The compare function for link channels — the FILE identity rules, for
|
|
89
|
+
* the file reason: a symlink cannot be retargeted in place (retarget is
|
|
90
|
+
* unlink + recreate, so the inode churns exactly like an atomic save),
|
|
91
|
+
* so the path absorbs that churn and claims first; the inode rescues
|
|
92
|
+
* path-orphans (`mv` preserves a symlink's inode). The payload is the
|
|
93
|
+
* target string, already read at walk time. A consequence worth naming:
|
|
94
|
+
* two links exchanging names read as two retargets (path-first),
|
|
95
|
+
* because a link's meaning IS its written address.
|
|
96
|
+
* 1. Path claims the channel: same target = continued (a new inode
|
|
97
|
+
* under an old name with an old target is silent recreation),
|
|
98
|
+
* different target = changed.
|
|
99
|
+
* 2. Inode rescues path-orphans: renames.
|
|
100
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
101
|
+
*/
|
|
102
|
+
export function diffLinkState(rows, entries) {
|
|
103
|
+
const changes = [];
|
|
104
|
+
const retags = [];
|
|
105
|
+
const rowsByPath = new Map(rows.map((row) => [row.relPath, row]));
|
|
106
|
+
const claimedRowIds = new Set();
|
|
107
|
+
const orphanEntries = [];
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const row = rowsByPath.get(entry.relPath);
|
|
110
|
+
if (!row) {
|
|
111
|
+
orphanEntries.push(entry);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
claimedRowIds.add(row.linkId);
|
|
115
|
+
if (row.target !== entry.target)
|
|
116
|
+
changes.push({ row, entry });
|
|
117
|
+
else if (row.device !== entry.device || row.inode !== entry.inode)
|
|
118
|
+
retags.push({ row, entry });
|
|
119
|
+
}
|
|
120
|
+
const orphanRows = rows.filter((row) => !claimedRowIds.has(row.linkId));
|
|
121
|
+
const renames = matchByInode(orphanRows, orphanEntries);
|
|
122
|
+
const renamedRowIds = new Set(renames.map((pair) => pair.row.linkId));
|
|
123
|
+
const renamedEntries = new Set(renames.map((pair) => pair.entry));
|
|
124
|
+
return {
|
|
125
|
+
changes,
|
|
126
|
+
retags,
|
|
127
|
+
renames,
|
|
128
|
+
creates: orphanEntries.filter((entry) => !renamedEntries.has(entry)),
|
|
129
|
+
deletes: orphanRows.filter((row) => !renamedRowIds.has(row.linkId)),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The compare function for file channels — doorbells and rescans both
|
|
134
|
+
* end here. Hashing is deferred — `checks` name the files whose content
|
|
135
|
+
* must be read before an event may be claimed.
|
|
136
|
+
*
|
|
137
|
+
* Identity rules, in order:
|
|
138
|
+
* 1. Path claims the channel. Same rel_path = same file_id, even when
|
|
139
|
+
* the inode changed (atomic saves replace the inode on every
|
|
140
|
+
* write).
|
|
141
|
+
* 2. Inode rescues path-orphans (matchByInode above).
|
|
142
|
+
* 3. Leftover entries are creates; leftover rows are deletes.
|
|
143
|
+
*
|
|
144
|
+
* The single stat-shortcut this layer allows itself: a (size, mtime,
|
|
145
|
+
* inode) triple identical to the stored one is skipped as unchanged —
|
|
146
|
+
* but only when the mtime is strictly older than the moment we last
|
|
147
|
+
* read the bytes (git's "racily clean" rule), and only when nothing
|
|
148
|
+
* pointed at the file. A path named in `mustCheck` (the doorbell said
|
|
149
|
+
* so) is always hashed: timestamps can lie, a delivered watcher event
|
|
150
|
+
* cannot be un-delivered.
|
|
151
|
+
*/
|
|
152
|
+
export function diffRootState(rows, entries, mustCheck = null) {
|
|
153
|
+
const checks = [];
|
|
154
|
+
let unchanged = 0;
|
|
155
|
+
const rowsByPath = new Map(rows.map((row) => [row.relPath, row]));
|
|
156
|
+
const claimedRowIds = new Set();
|
|
157
|
+
const orphanEntries = [];
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const row = rowsByPath.get(entry.relPath);
|
|
160
|
+
if (!row) {
|
|
161
|
+
orphanEntries.push(entry);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
claimedRowIds.add(row.fileId);
|
|
165
|
+
const statsMatch = row.size === entry.size && row.mtimeMs === entry.mtimeMs && row.inode === entry.inode;
|
|
166
|
+
const provablyBeforeLastRead = typeof row.hashedAtMs === 'number' && entry.mtimeMs < row.hashedAtMs;
|
|
167
|
+
const named = mustCheck !== null && mustCheck.has(entry.relPath);
|
|
168
|
+
if (statsMatch && provablyBeforeLastRead && !named) {
|
|
169
|
+
unchanged += 1;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
checks.push({ row, entry });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const orphanRows = rows.filter((row) => !claimedRowIds.has(row.fileId));
|
|
176
|
+
const renames = matchByInode(orphanRows, orphanEntries);
|
|
177
|
+
const renamedRowIds = new Set(renames.map((pair) => pair.row.fileId));
|
|
178
|
+
const renamedEntries = new Set(renames.map((pair) => pair.entry));
|
|
179
|
+
return {
|
|
180
|
+
checks,
|
|
181
|
+
renames,
|
|
182
|
+
creates: orphanEntries.filter((entry) => !renamedEntries.has(entry)),
|
|
183
|
+
deletes: orphanRows.filter((row) => !renamedRowIds.has(row.fileId)),
|
|
184
|
+
unchanged,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/detection/diff.ts"],"names":[],"mappings":"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"}
|