harnery 0.45.1 → 0.46.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/dist/commander.d.ts +13 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commands/admission.d.ts.map +1 -1
- package/dist/commands/admission.js +214 -27
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +102 -25
- package/dist/commands/browse.d.ts +3 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +172 -8
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +8 -2
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +40 -0
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +4 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +33 -6
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +6 -45
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +0 -18
- package/dist/core/agents/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -4
- package/dist/core/agents/render/session-context.d.ts +0 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +1 -6
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -4
- package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +6 -2
- package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
- package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference-contract.js +1 -0
- package/dist/core/agents/state/parent-reference.d.ts +18 -0
- package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference.js +119 -0
- package/dist/core/agents/state/pidmap.js +2 -2
- package/dist/core/agents/state/proc-start.d.ts +20 -31
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +37 -70
- package/dist/core/agents/state/service-process.d.ts +61 -0
- package/dist/core/agents/state/service-process.d.ts.map +1 -0
- package/dist/core/agents/state/service-process.js +143 -0
- package/dist/core/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +63 -0
- package/dist/core/context/index.d.ts +6 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +194 -256
- package/dist/core/context/observation.d.ts +9 -9
- package/dist/core/context/observation.d.ts.map +1 -1
- package/dist/core/context/observation.js +37 -6
- package/dist/core/diagnostics/path-identity.d.ts +1 -1
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
- package/dist/core/diagnostics/path-identity.js +51 -6
- package/dist/core/events/v3/canonical.js +43 -14
- package/dist/core/events/v3/index.d.ts +1 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +1 -1
- package/dist/core/events/v3/producers/recorder.d.ts +34 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +117 -13
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +53 -13
- package/dist/core/events/v3/runtime-build.d.ts +14 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +44 -0
- package/dist/core/events/v3/validate.d.ts.map +1 -1
- package/dist/core/events/v3/validate.js +36 -1
- package/dist/core/governor/index.d.ts +2 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +2 -1
- package/dist/core/governor/service-read.d.ts +3 -10
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +10 -94
- package/dist/core/governor/service.d.ts +3 -7
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +17 -182
- package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
- package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
- package/dist/core/hooks/adapter/native-parent.js +91 -0
- package/dist/core/hooks/cli.js +79 -4
- package/dist/core/hooks/resolve/owner.d.ts +0 -11
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +2 -25
- package/dist/core/hooks/shell-admission.d.ts +84 -0
- package/dist/core/hooks/shell-admission.d.ts.map +1 -0
- package/dist/core/hooks/shell-admission.js +622 -0
- package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
- package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
- package/dist/core/hooks/shell-commit-tree.js +104 -0
- package/dist/core/inbox/contract.d.ts +12 -0
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/contract.js +16 -0
- package/dist/core/inbox/peer-messages.d.ts +41 -16
- package/dist/core/inbox/peer-messages.d.ts.map +1 -1
- package/dist/core/inbox/peer-messages.js +372 -15
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +83 -3
- package/dist/core/inbox/targets.d.ts +17 -3
- package/dist/core/inbox/targets.d.ts.map +1 -1
- package/dist/core/inbox/targets.js +77 -2
- package/dist/core/presence/git.d.ts +18 -12
- package/dist/core/presence/git.d.ts.map +1 -1
- package/dist/core/presence/git.js +32 -38
- package/dist/core/presence/index.d.ts +2 -2
- package/dist/core/presence/index.d.ts.map +1 -1
- package/dist/core/presence/index.js +8 -6
- package/dist/core/presence/mail.d.ts +133 -0
- package/dist/core/presence/mail.d.ts.map +1 -0
- package/dist/core/presence/mail.js +464 -0
- package/dist/core/resources/sampler.js +2 -2
- package/dist/core/resources/service-status.d.ts +2 -14
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +3 -23
- package/dist/core/resources/service.d.ts +3 -7
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +12 -101
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +3 -2
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +9 -2
- package/dist/core/servers/index.d.ts +0 -2
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +3 -18
- package/dist/core/storage/modes.d.ts +4 -0
- package/dist/core/storage/modes.d.ts.map +1 -1
- package/dist/core/storage/modes.js +4 -0
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +5 -4
- package/dist/core/supervisor/service.d.ts +4 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +23 -122
- package/dist/core/supervisor/services.js +2 -2
- package/dist/core/supervisor/status.d.ts +3 -17
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +7 -70
- package/dist/core/workflow/engine.js +2 -1
- package/dist/core/workflow/index.d.ts +3 -4
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +30 -74
- package/dist/core/workflow/resume-lock.d.ts +17 -0
- package/dist/core/workflow/resume-lock.d.ts.map +1 -0
- package/dist/core/workflow/resume-lock.js +158 -0
- package/dist/core/workflow/run-state.d.ts +0 -21
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -174
- package/dist/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -1
- package/dist/core/workflow/workspaces/local-git.js +91 -210
- package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/ownership.js +3 -130
- package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +0 -4
- package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +1 -4
- package/dist/lib/admission-history.d.ts +96 -0
- package/dist/lib/admission-history.d.ts.map +1 -0
- package/dist/lib/admission-history.js +196 -0
- package/dist/lib/admission.d.ts +56 -7
- package/dist/lib/admission.d.ts.map +1 -1
- package/dist/lib/admission.js +161 -19
- package/dist/lib/browser/client.d.ts +18 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +100 -12
- package/dist/lib/browser/content-checks.d.ts +3 -1
- package/dist/lib/browser/content-checks.d.ts.map +1 -1
- package/dist/lib/browser/content-checks.js +10 -2
- package/dist/lib/browser/critique.d.ts +32 -0
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +72 -3
- package/dist/lib/browser/geometry.d.ts +2 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +61 -15
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -1
- package/dist/lib/browser/page-review-contracts.d.ts +6 -1
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
- package/dist/lib/browser/page-review-contracts.js +6 -1
- package/dist/lib/browser/page-review-judge.d.ts +3 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +41 -11
- package/dist/lib/browser/page-review-pack.d.ts +3 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +5 -2
- package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +11 -4
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +73 -16
- package/dist/lib/browser/runts.d.ts +4 -2
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +84 -33
- package/dist/lib/browser/windows-chrome.d.ts +14 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +42 -0
- package/dist/lib/browser/windows-login-sync.d.ts +110 -0
- package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
- package/dist/lib/browser/windows-login-sync.js +171 -0
- package/dist/lib/cookies/client.d.ts +30 -5
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +55 -10
- package/dist/lib/cookies/index.d.ts +1 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +1 -1
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
- package/dist/lib/docs-metadata-sync.js +41 -2
- package/dist/lib/durable-job.d.ts +8 -0
- package/dist/lib/durable-job.d.ts.map +1 -1
- package/dist/lib/durable-job.js +10 -0
- package/dist/lib/http/client.d.ts +9 -8
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +44 -2
- package/dist/lib/instructions/templates.js +4 -4
- package/package.json +2 -2
- package/src/commander.ts +13 -0
- package/src/commands/admission.ts +272 -29
- package/src/commands/agents.ts +132 -27
- package/src/commands/browse.ts +208 -6
- package/src/commands/fetch.ts +1 -0
- package/src/commands/ledger-v3.ts +8 -2
- package/src/commands/presence.ts +49 -0
- package/src/commands/qa-run.ts +4 -3
- package/src/commands/review-pack.ts +41 -5
- package/src/commands/workflow.ts +8 -58
- package/src/core/agents/coord-client.ts +0 -20
- package/src/core/agents/index.ts +6 -0
- package/src/core/agents/live-authority-v3.ts +11 -4
- package/src/core/agents/render/session-context.ts +1 -7
- package/src/core/agents/session-events.ts +18 -3
- package/src/core/agents/state/heartbeat-reader.ts +3 -0
- package/src/core/agents/state/live-coordination-view.ts +13 -6
- package/src/core/agents/state/parent-reference-contract.ts +14 -0
- package/src/core/agents/state/parent-reference.ts +179 -0
- package/src/core/agents/state/pidmap.ts +2 -2
- package/src/core/agents/state/proc-start.ts +41 -69
- package/src/core/agents/state/service-process.ts +217 -0
- package/src/core/config.ts +85 -0
- package/src/core/context/index.ts +207 -272
- package/src/core/context/observation.ts +40 -9
- package/src/core/diagnostics/path-identity.ts +52 -6
- package/src/core/events/v3/canonical.ts +46 -13
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/producers/recorder.ts +151 -13
- package/src/core/events/v3/reader.ts +55 -11
- package/src/core/events/v3/runtime-build.ts +62 -0
- package/src/core/events/v3/validate.ts +42 -1
- package/src/core/governor/index.ts +1 -2
- package/src/core/governor/service-read.ts +15 -114
- package/src/core/governor/service.ts +36 -220
- package/src/core/hooks/adapter/native-parent.ts +109 -0
- package/src/core/hooks/cli.ts +83 -4
- package/src/core/hooks/resolve/owner.ts +2 -25
- package/src/core/hooks/shell-admission.ts +634 -0
- package/src/core/hooks/shell-commit-tree.ts +102 -0
- package/src/core/inbox/contract.ts +18 -0
- package/src/core/inbox/peer-messages.ts +501 -15
- package/src/core/inbox/service.ts +108 -3
- package/src/core/inbox/targets.ts +94 -3
- package/src/core/presence/git.ts +45 -37
- package/src/core/presence/index.ts +16 -8
- package/src/core/presence/mail.ts +620 -0
- package/src/core/resources/sampler.ts +3 -2
- package/src/core/resources/service-status.ts +6 -35
- package/src/core/resources/service.ts +23 -133
- package/src/core/resources/status.ts +3 -2
- package/src/core/semantic/service.ts +8 -2
- package/src/core/servers/index.ts +3 -23
- package/src/core/storage/modes.ts +6 -0
- package/src/core/storage/segments.ts +5 -4
- package/src/core/supervisor/service.ts +33 -155
- package/src/core/supervisor/services.ts +2 -2
- package/src/core/supervisor/status.ts +10 -85
- package/src/core/workflow/engine.ts +1 -1
- package/src/core/workflow/index.ts +2 -13
- package/src/core/workflow/proof.ts +35 -83
- package/src/core/workflow/resume-lock.ts +179 -0
- package/src/core/workflow/run-state.ts +3 -207
- package/src/core/workflow/workspaces/leases.ts +1 -1
- package/src/core/workflow/workspaces/local-git.ts +120 -239
- package/src/core/workflow/workspaces/ownership.ts +3 -158
- package/src/core/workflow/workspaces/reclaim.ts +3 -9
- package/src/core/workflow/workspaces/recovery.ts +1 -1
- package/src/core/workflow/workspaces/state.ts +1 -5
- package/src/lib/admission-history.ts +267 -0
- package/src/lib/admission.ts +217 -19
- package/src/lib/browser/client.ts +147 -35
- package/src/lib/browser/content-checks.ts +14 -3
- package/src/lib/browser/critique.ts +113 -3
- package/src/lib/browser/geometry.ts +63 -14
- package/src/lib/browser/index.ts +5 -0
- package/src/lib/browser/page-review-contracts.ts +6 -1
- package/src/lib/browser/page-review-judge.ts +58 -13
- package/src/lib/browser/page-review-pack.ts +13 -2
- package/src/lib/browser/qa-run-contracts.ts +30 -4
- package/src/lib/browser/qa-run.ts +78 -17
- package/src/lib/browser/runts.ts +85 -37
- package/src/lib/browser/windows-chrome.ts +50 -0
- package/src/lib/browser/windows-login-sync.ts +275 -0
- package/src/lib/cookies/client.ts +62 -10
- package/src/lib/cookies/index.ts +3 -0
- package/src/lib/docs-metadata-sync.ts +44 -2
- package/src/lib/durable-job.ts +15 -0
- package/src/lib/http/client.ts +63 -1
- package/src/lib/instructions/templates.ts +4 -4
- package/dist/core/agents/state/process-handle.d.ts +0 -26
- package/dist/core/agents/state/process-handle.d.ts.map +0 -1
- package/dist/core/agents/state/process-handle.js +0 -121
- package/dist/core/workflow/recovery-audit.d.ts +0 -30
- package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
- package/dist/core/workflow/recovery-audit.js +0 -116
- package/src/core/agents/state/process-handle.ts +0 -165
- package/src/core/workflow/recovery-audit.ts +0 -181
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import {
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
HARNERY_INBOX_STATUS_SCHEMA,
|
|
13
13
|
HarneryInboxCapacityError,
|
|
14
14
|
type HarneryInboxLimits,
|
|
15
|
+
HarneryInboxMessageConflictError,
|
|
16
|
+
HarneryInboxMessageHistoryMissingError,
|
|
15
17
|
type HarneryInboxMessageRecordV1,
|
|
16
18
|
type HarneryInboxRecordV1,
|
|
17
19
|
type HarneryInboxStatusV1,
|
|
@@ -27,6 +29,8 @@ export interface HarneryInboxServiceOptions {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface HarneryInboxSendInput {
|
|
32
|
+
message_id?: string;
|
|
33
|
+
replay_only?: boolean;
|
|
30
34
|
sender_instance_id: string | null;
|
|
31
35
|
sender_display_name: string;
|
|
32
36
|
recipient_instance_id: string;
|
|
@@ -35,12 +39,22 @@ export interface HarneryInboxSendInput {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface HarneryInboxSendReceipt {
|
|
38
|
-
state: "queued";
|
|
42
|
+
state: "queued" | "duplicate";
|
|
39
43
|
message_id: string;
|
|
40
44
|
body_bytes: number;
|
|
41
45
|
synced: true;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/** A caller freezes the operation and recipient before its first attempt. */
|
|
49
|
+
export function stableInboxMessageId(operationId: string, recipientInstanceId: string): string {
|
|
50
|
+
const identity = JSON.stringify([
|
|
51
|
+
"harnery.inbox-operation/v1",
|
|
52
|
+
opaqueId(operationId, "operation"),
|
|
53
|
+
opaqueId(recipientInstanceId, "recipient instance"),
|
|
54
|
+
]);
|
|
55
|
+
return `msg_op_${createHash("sha256").update(identity).digest("hex")}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
export class HarneryInboxService {
|
|
45
59
|
readonly #options: HarneryInboxServiceOptions;
|
|
46
60
|
|
|
@@ -50,6 +64,12 @@ export class HarneryInboxService {
|
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
send(input: HarneryInboxSendInput): HarneryInboxSendReceipt {
|
|
67
|
+
if (input.replay_only !== undefined && typeof input.replay_only !== "boolean") {
|
|
68
|
+
throw new Error("invalid replay_only flag");
|
|
69
|
+
}
|
|
70
|
+
if (input.replay_only && input.message_id === undefined) {
|
|
71
|
+
throw new Error("replay requires explicit message id");
|
|
72
|
+
}
|
|
53
73
|
const recipient = opaqueId(input.recipient_instance_id, "recipient instance");
|
|
54
74
|
const bodyBytes = Buffer.byteLength(input.body);
|
|
55
75
|
const limits = this.#options.limits;
|
|
@@ -63,7 +83,10 @@ export class HarneryInboxService {
|
|
|
63
83
|
const record: HarneryInboxMessageRecordV1 = {
|
|
64
84
|
schema: HARNERY_INBOX_RECORD_SCHEMA,
|
|
65
85
|
kind: "message",
|
|
66
|
-
message_id:
|
|
86
|
+
message_id:
|
|
87
|
+
input.message_id === undefined
|
|
88
|
+
? `msg_${this.#options.id?.() ?? randomUUID()}`
|
|
89
|
+
: opaqueId(input.message_id, "message"),
|
|
67
90
|
sender_instance_id: senderId(input.sender_instance_id),
|
|
68
91
|
sender_display_name: boundedDisplay(input.sender_display_name),
|
|
69
92
|
recipient_instance_id: recipient,
|
|
@@ -78,6 +101,24 @@ export class HarneryInboxService {
|
|
|
78
101
|
readOptions(limits),
|
|
79
102
|
(records) => {
|
|
80
103
|
const projection = project(records);
|
|
104
|
+
const existing = records.find(
|
|
105
|
+
(item): item is HarneryInboxMessageRecordV1 =>
|
|
106
|
+
item.kind === "message" && item.message_id === record.message_id,
|
|
107
|
+
);
|
|
108
|
+
if (existing) {
|
|
109
|
+
assertSameMessage(existing, record);
|
|
110
|
+
return {
|
|
111
|
+
result: {
|
|
112
|
+
state: "duplicate",
|
|
113
|
+
message_id: existing.message_id,
|
|
114
|
+
body_bytes: existing.body_bytes,
|
|
115
|
+
synced: true,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (input.replay_only) {
|
|
120
|
+
throw new HarneryInboxMessageHistoryMissingError(record.message_id);
|
|
121
|
+
}
|
|
81
122
|
capacityCheck(projection, record, path, limits);
|
|
82
123
|
return {
|
|
83
124
|
append: [record],
|
|
@@ -92,6 +133,52 @@ export class HarneryInboxService {
|
|
|
92
133
|
);
|
|
93
134
|
}
|
|
94
135
|
|
|
136
|
+
/** Append a message created on another machine, keeping its id. Importers
|
|
137
|
+
* on one machine can race, so the duplicate check runs under the lease. */
|
|
138
|
+
receive(record: HarneryInboxMessageRecordV1): "received" | "duplicate" {
|
|
139
|
+
const recipient = opaqueId(record.recipient_instance_id, "recipient instance");
|
|
140
|
+
const limits = this.#options.limits;
|
|
141
|
+
if (record.body_bytes === 0 || record.body_bytes > limits.max_message_body_bytes) {
|
|
142
|
+
throw new HarneryInboxCapacityError(
|
|
143
|
+
"message_body_limit",
|
|
144
|
+
record.body_bytes,
|
|
145
|
+
limits.max_message_body_bytes,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const incoming: HarneryInboxMessageRecordV1 = {
|
|
149
|
+
schema: HARNERY_INBOX_RECORD_SCHEMA,
|
|
150
|
+
kind: "message",
|
|
151
|
+
message_id: opaqueId(record.message_id, "message"),
|
|
152
|
+
sender_instance_id: senderId(record.sender_instance_id),
|
|
153
|
+
sender_display_name: boundedDisplay(record.sender_display_name),
|
|
154
|
+
...(record.sender_machine ? { sender_machine: boundedDisplay(record.sender_machine) } : {}),
|
|
155
|
+
recipient_instance_id: recipient,
|
|
156
|
+
recipient_display_name: boundedDisplay(record.recipient_display_name),
|
|
157
|
+
created_at: record.created_at,
|
|
158
|
+
body: record.body,
|
|
159
|
+
body_bytes: record.body_bytes,
|
|
160
|
+
};
|
|
161
|
+
validateRecord(incoming);
|
|
162
|
+
const path = inboxPath(this.#options.coord_root, recipient);
|
|
163
|
+
return updateInboxHistory<HarneryInboxRecordV1, "received" | "duplicate">(
|
|
164
|
+
path,
|
|
165
|
+
readOptions(limits),
|
|
166
|
+
(records) => {
|
|
167
|
+
const projection = project(records);
|
|
168
|
+
const existing = records.find(
|
|
169
|
+
(item): item is HarneryInboxMessageRecordV1 =>
|
|
170
|
+
item.kind === "message" && item.message_id === incoming.message_id,
|
|
171
|
+
);
|
|
172
|
+
if (existing) {
|
|
173
|
+
assertSameMessage(existing, incoming);
|
|
174
|
+
return { result: "duplicate" };
|
|
175
|
+
}
|
|
176
|
+
capacityCheck(projection, incoming, path, limits);
|
|
177
|
+
return { append: [incoming], result: "received" };
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
95
182
|
records(recipientInstanceId: string): HarneryInboxRecordV1[] {
|
|
96
183
|
const records = readCrashSafeJsonlFile<HarneryInboxRecordV1>(
|
|
97
184
|
inboxPath(this.#options.coord_root, opaqueId(recipientInstanceId, "recipient instance")),
|
|
@@ -202,6 +289,24 @@ export class HarneryInboxService {
|
|
|
202
289
|
}
|
|
203
290
|
}
|
|
204
291
|
|
|
292
|
+
function assertSameMessage(
|
|
293
|
+
existing: HarneryInboxMessageRecordV1,
|
|
294
|
+
incoming: HarneryInboxMessageRecordV1,
|
|
295
|
+
): void {
|
|
296
|
+
const fields = [
|
|
297
|
+
"sender_instance_id",
|
|
298
|
+
"sender_display_name",
|
|
299
|
+
"sender_machine",
|
|
300
|
+
"recipient_instance_id",
|
|
301
|
+
"recipient_display_name",
|
|
302
|
+
"body",
|
|
303
|
+
"body_bytes",
|
|
304
|
+
] as const;
|
|
305
|
+
if (fields.some((field) => existing[field] !== incoming[field])) {
|
|
306
|
+
throw new HarneryInboxMessageConflictError(incoming.message_id);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
205
310
|
/** The shared writer refuses an occupied lease before invoking the update.
|
|
206
311
|
* Briefly retry only that exact refusal so concurrent peer senders both get
|
|
207
312
|
* a chance to append. All validation, authority and I/O failures still fail
|
|
@@ -11,10 +11,14 @@ import {
|
|
|
11
11
|
nativeInstanceIdV3,
|
|
12
12
|
observeLiveEventLedgerRouteV3,
|
|
13
13
|
} from "../events/v3/live-route-observer.ts";
|
|
14
|
+
import { readRemoteMachines } from "../presence/index.ts";
|
|
15
|
+
import { knownRemoteSenders } from "../presence/mail.ts";
|
|
14
16
|
|
|
15
17
|
export interface MessageTargetInput {
|
|
16
18
|
name?: string;
|
|
17
19
|
instanceId?: string;
|
|
20
|
+
/** Refuse a later generation of the same immutable instance. */
|
|
21
|
+
generationId?: string;
|
|
18
22
|
requireLive?: boolean;
|
|
19
23
|
now?: Date;
|
|
20
24
|
}
|
|
@@ -22,11 +26,17 @@ export interface MessageTargetInput {
|
|
|
22
26
|
export type MessageTargetResult =
|
|
23
27
|
| {
|
|
24
28
|
ok: true;
|
|
25
|
-
target: {
|
|
29
|
+
target: {
|
|
30
|
+
instance_id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
delivery: "written-live" | "queued" | "remote";
|
|
33
|
+
/** Set when the recipient lives on another machine (ADR 0222). */
|
|
34
|
+
machine?: string;
|
|
35
|
+
};
|
|
26
36
|
}
|
|
27
37
|
| {
|
|
28
38
|
ok: false;
|
|
29
|
-
code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe";
|
|
39
|
+
code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe" | "generation_mismatch";
|
|
30
40
|
message: string;
|
|
31
41
|
candidates?: string[];
|
|
32
42
|
};
|
|
@@ -35,6 +45,7 @@ export type MessageTargetResult =
|
|
|
35
45
|
export function resolveMessageTarget(
|
|
36
46
|
coordRoot: string,
|
|
37
47
|
input: MessageTargetInput,
|
|
48
|
+
options: { view?: CoordinationViewV3 } = {},
|
|
38
49
|
): MessageTargetResult {
|
|
39
50
|
const requestedName = normalizeName(input.name);
|
|
40
51
|
const requestedInstance = input.instanceId?.trim();
|
|
@@ -49,7 +60,9 @@ export function resolveMessageTarget(
|
|
|
49
60
|
if (observeLiveEventLedgerRouteV3(coordRoot).state === "blocked") {
|
|
50
61
|
return unsafe("The event ledger cannot establish message recipient authority.");
|
|
51
62
|
}
|
|
52
|
-
view = requireAuthoritySafeCoordinationViewV3(
|
|
63
|
+
view = requireAuthoritySafeCoordinationViewV3(
|
|
64
|
+
options.view ?? readCoordinationViewV3(coordRoot),
|
|
65
|
+
);
|
|
53
66
|
if (!view.source_complete) return unsafe("The event ledger read is incomplete.");
|
|
54
67
|
} catch {
|
|
55
68
|
return unsafe("The event ledger cannot establish message recipient authority.");
|
|
@@ -79,6 +92,7 @@ export function resolveMessageTarget(
|
|
|
79
92
|
{
|
|
80
93
|
instanceId,
|
|
81
94
|
name,
|
|
95
|
+
generationId: generation.generation_id,
|
|
82
96
|
safe: !diagnostics.some((diagnostic) => diagnostic.authority_blocking),
|
|
83
97
|
fresh:
|
|
84
98
|
generation.phase === "live" && Number.isFinite(age) && age >= 0 && age <= freshnessMs,
|
|
@@ -107,6 +121,13 @@ export function resolveMessageTarget(
|
|
|
107
121
|
};
|
|
108
122
|
}
|
|
109
123
|
const target = selected[0]!;
|
|
124
|
+
if (input.generationId && input.generationId !== target.generationId) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
code: "generation_mismatch",
|
|
128
|
+
message: "The recipient generation differs from the exact requested generation.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
110
131
|
if (input.requireLive && !target.fresh) {
|
|
111
132
|
return {
|
|
112
133
|
ok: false,
|
|
@@ -125,6 +146,76 @@ export function resolveMessageTarget(
|
|
|
125
146
|
};
|
|
126
147
|
}
|
|
127
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Resolve a recipient on another machine, for a name or instance this
|
|
151
|
+
* machine's ledger has never seen. Candidates come from peer presence (live
|
|
152
|
+
* or stale) and from senders this machine has received mail from. Returns
|
|
153
|
+
* null when nothing matches, so the caller can report the local miss.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveRemoteMessageTarget(
|
|
156
|
+
coordRoot: string,
|
|
157
|
+
input: MessageTargetInput,
|
|
158
|
+
): MessageTargetResult | null {
|
|
159
|
+
const requestedName = normalizeName(input.name);
|
|
160
|
+
const requestedInstance = input.instanceId?.trim();
|
|
161
|
+
if (!requestedName && !requestedInstance) return null;
|
|
162
|
+
// Presence/mail names do not prove an exact V3 generation.
|
|
163
|
+
if (input.generationId) return null;
|
|
164
|
+
const live = new Set(
|
|
165
|
+
readRemoteMachines(coordRoot).flatMap(({ agents }) => agents.map((a) => a.instance_id)),
|
|
166
|
+
);
|
|
167
|
+
const known = new Map<string, { instanceId: string; name: string; machine: string }>();
|
|
168
|
+
for (const { machine, agents } of readRemoteMachines(coordRoot, { includeStale: true })) {
|
|
169
|
+
for (const agent of agents) {
|
|
170
|
+
if (agent.name)
|
|
171
|
+
known.set(agent.instance_id, { instanceId: agent.instance_id, name: agent.name, machine });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const sender of knownRemoteSenders(coordRoot)) {
|
|
175
|
+
if (!known.has(sender.instance_id)) {
|
|
176
|
+
known.set(sender.instance_id, {
|
|
177
|
+
instanceId: sender.instance_id,
|
|
178
|
+
name: sender.name,
|
|
179
|
+
machine: sender.machine,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const candidates = [...known.values()]
|
|
184
|
+
.filter(({ instanceId }) => !requestedInstance || instanceId === requestedInstance)
|
|
185
|
+
.filter(({ name }) => !requestedName || normalizeName(name) === requestedName)
|
|
186
|
+
.map((candidate) => ({ ...candidate, fresh: live.has(candidate.instanceId) }));
|
|
187
|
+
if (candidates.length === 0) return null;
|
|
188
|
+
const fresh = candidates.filter(({ fresh }) => fresh);
|
|
189
|
+
const selected = requestedInstance ? candidates : fresh.length > 0 ? fresh : candidates;
|
|
190
|
+
if (selected.length !== 1) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
code: "ambiguous_peer",
|
|
194
|
+
message:
|
|
195
|
+
"Several agent instances on other machines hold that exact name; specify --instance-id.",
|
|
196
|
+
candidates: selected.map(({ instanceId, machine }) => `${instanceId} (${machine})`).sort(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const target = selected[0]!;
|
|
200
|
+
if (input.requireLive && !target.fresh) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
code: "not_live",
|
|
204
|
+
message:
|
|
205
|
+
"That agent is on another machine and outside live presence; omit --require-live to queue its message.",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
target: {
|
|
211
|
+
instance_id: target.instanceId,
|
|
212
|
+
name: target.name,
|
|
213
|
+
delivery: "remote",
|
|
214
|
+
machine: target.machine,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
128
219
|
function laterPosition(
|
|
129
220
|
left: CoordinationGenerationViewV3,
|
|
130
221
|
right: CoordinationGenerationViewV3,
|
package/src/core/presence/git.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Git plumbing for the cross-machine presence transport (ADR 0016)
|
|
2
|
+
* Git plumbing for the cross-machine presence transport (ADR 0016) and the
|
|
3
|
+
* mail refs that ride on it (ADR 0222).
|
|
3
4
|
*
|
|
4
5
|
* Presence blobs travel as parentless commits (empty tree, JSON message)
|
|
5
6
|
* force-pushed to `refs/harnery/presence/<machine>` on the host repo's own
|
|
@@ -15,9 +16,12 @@
|
|
|
15
16
|
|
|
16
17
|
import { spawn, spawnSync } from "node:child_process";
|
|
17
18
|
|
|
18
|
-
/** The ref
|
|
19
|
-
* publisher is structurally unable to touch refs/heads/* (ADR 0016).
|
|
19
|
+
/** The ref namespaces. Hardcoded — never interpolated from config — so the
|
|
20
|
+
* publisher is structurally unable to touch refs/heads/* (ADR 0016). Mail
|
|
21
|
+
* refs carry cross-machine peer messages on the same transport (ADR 0222). */
|
|
20
22
|
export const PRESENCE_REF_PREFIX = "refs/harnery/presence/";
|
|
23
|
+
export const MAIL_REF_PREFIX = "refs/harnery/mail/";
|
|
24
|
+
type HarneryRefPrefix = typeof PRESENCE_REF_PREFIX | typeof MAIL_REF_PREFIX;
|
|
21
25
|
|
|
22
26
|
/** Well-known SHA of git's empty tree; `git mktree` below guarantees the
|
|
23
27
|
* object exists in the local odb before we build a commit on it. */
|
|
@@ -112,44 +116,46 @@ export function writePresenceCommit(root: string, message: string): string | nul
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/**
|
|
115
|
-
* Force-push a
|
|
116
|
-
*
|
|
117
|
-
*
|
|
119
|
+
* Force-push a commit to `<prefix><machine>` (presence or mail). `sync: true`
|
|
120
|
+
* (CLI/tests) waits and reports; default is detached fire-and-forget (hook
|
|
121
|
+
* path — a slow or credential-less push must not block a turn).
|
|
118
122
|
*/
|
|
119
|
-
export function
|
|
123
|
+
export function pushMachineRef(
|
|
120
124
|
root: string,
|
|
125
|
+
prefix: HarneryRefPrefix,
|
|
121
126
|
machine: string,
|
|
122
127
|
sha: string,
|
|
123
128
|
opts: { sync?: boolean } = {},
|
|
124
129
|
): { ok: boolean; error?: string } {
|
|
125
|
-
const refspec = `+${sha}:${
|
|
126
|
-
|
|
127
|
-
if (opts.sync) {
|
|
128
|
-
const r = runGit(root, args.slice(2));
|
|
129
|
-
return r.ok ? { ok: true } : { ok: false, error: r.stderr.trim().slice(0, 300) };
|
|
130
|
-
}
|
|
131
|
-
try {
|
|
132
|
-
const child = spawn("git", args, { detached: true, stdio: "ignore", env: gitEnv() });
|
|
133
|
-
child.unref();
|
|
134
|
-
return { ok: true };
|
|
135
|
-
} catch {
|
|
136
|
-
return { ok: false, error: "spawn failed" };
|
|
137
|
-
}
|
|
130
|
+
const refspec = `+${sha}:${prefix}${sanitizeRefComponent(machine)}`;
|
|
131
|
+
return runNetworkGit(root, ["push", "--quiet", "--no-verify", "origin", refspec], opts);
|
|
138
132
|
}
|
|
139
133
|
|
|
140
|
-
/** Fetch every peer presence ref from origin into the local ref
|
|
141
|
-
|
|
134
|
+
/** Fetch every peer presence and mail ref from origin into the local ref
|
|
135
|
+
* namespace, in one round trip. A namespace with no refs is not an error. */
|
|
136
|
+
export function fetchMachineRefs(
|
|
142
137
|
root: string,
|
|
143
138
|
opts: { sync?: boolean } = {},
|
|
144
139
|
): { ok: boolean; error?: string } {
|
|
145
|
-
const
|
|
146
|
-
|
|
140
|
+
const refspecs = [PRESENCE_REF_PREFIX, MAIL_REF_PREFIX].map((p) => `+${p}*:${p}*`);
|
|
141
|
+
return runNetworkGit(root, ["fetch", "--quiet", "--no-tags", "origin", ...refspecs], opts);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function runNetworkGit(
|
|
145
|
+
root: string,
|
|
146
|
+
args: string[],
|
|
147
|
+
opts: { sync?: boolean },
|
|
148
|
+
): { ok: boolean; error?: string } {
|
|
147
149
|
if (opts.sync) {
|
|
148
|
-
const r = runGit(root, args
|
|
150
|
+
const r = runGit(root, args);
|
|
149
151
|
return r.ok ? { ok: true } : { ok: false, error: r.stderr.trim().slice(0, 300) };
|
|
150
152
|
}
|
|
151
153
|
try {
|
|
152
|
-
const child = spawn("git",
|
|
154
|
+
const child = spawn("git", ["-C", root, ...args], {
|
|
155
|
+
detached: true,
|
|
156
|
+
stdio: "ignore",
|
|
157
|
+
env: gitEnv(),
|
|
158
|
+
});
|
|
153
159
|
child.unref();
|
|
154
160
|
return { ok: true };
|
|
155
161
|
} catch {
|
|
@@ -157,15 +163,14 @@ export function fetchPresenceRefs(
|
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
/** Read every locally-known
|
|
161
|
-
export function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
]);
|
|
166
|
+
/** Read every locally-known ref under `prefix` → [{machine, message}]. */
|
|
167
|
+
export function readMachineRefs(
|
|
168
|
+
root: string,
|
|
169
|
+
prefix: HarneryRefPrefix = PRESENCE_REF_PREFIX,
|
|
170
|
+
): Array<{ machine: string; message: string }> {
|
|
171
|
+
const r = runGit(root, ["for-each-ref", "--format=%(refname)%00%(contents)%01", `${prefix}*`]);
|
|
167
172
|
if (!r.ok) return [];
|
|
168
|
-
return parseForEachRefOutput(r.stdout);
|
|
173
|
+
return parseForEachRefOutput(r.stdout, prefix);
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
/**
|
|
@@ -173,7 +178,10 @@ export function readPresenceRefs(root: string): Array<{ machine: string; message
|
|
|
173
178
|
* Records are %01-terminated (contents may contain newlines); refname and
|
|
174
179
|
* contents are %00-separated. Exported for unit tests.
|
|
175
180
|
*/
|
|
176
|
-
export function parseForEachRefOutput(
|
|
181
|
+
export function parseForEachRefOutput(
|
|
182
|
+
out: string,
|
|
183
|
+
prefix: HarneryRefPrefix = PRESENCE_REF_PREFIX,
|
|
184
|
+
): Array<{ machine: string; message: string }> {
|
|
177
185
|
const rows: Array<{ machine: string; message: string }> = [];
|
|
178
186
|
for (const rec of out.split("\u0001")) {
|
|
179
187
|
const trimmed = rec.replace(/^\n+/, "");
|
|
@@ -181,9 +189,9 @@ export function parseForEachRefOutput(out: string): Array<{ machine: string; mes
|
|
|
181
189
|
const sep = trimmed.indexOf("\u0000");
|
|
182
190
|
if (sep === -1) continue;
|
|
183
191
|
const refname = trimmed.slice(0, sep).trim();
|
|
184
|
-
if (!refname.startsWith(
|
|
192
|
+
if (!refname.startsWith(prefix)) continue;
|
|
185
193
|
rows.push({
|
|
186
|
-
machine: refname.slice(
|
|
194
|
+
machine: refname.slice(prefix.length),
|
|
187
195
|
message: trimmed.slice(sep + 1).trim(),
|
|
188
196
|
});
|
|
189
197
|
}
|
|
@@ -31,17 +31,23 @@ import { resolveMachineLabel } from "../../lib/machine.ts";
|
|
|
31
31
|
import { presenceEnabled } from "../config.ts";
|
|
32
32
|
import { buildPresenceBlob, type PresenceAgent, type PresenceBlob } from "./blob.ts";
|
|
33
33
|
import {
|
|
34
|
-
|
|
34
|
+
fetchMachineRefs,
|
|
35
35
|
hasOrigin,
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
PRESENCE_REF_PREFIX,
|
|
37
|
+
pushMachineRef,
|
|
38
|
+
readMachineRefs,
|
|
38
39
|
sanitizeRefComponent,
|
|
39
40
|
writePresenceCommit,
|
|
40
41
|
} from "./git.ts";
|
|
41
42
|
|
|
42
43
|
export type { PresenceAgent, PresenceBlob } from "./blob.ts";
|
|
43
44
|
export { buildPresenceBlob } from "./blob.ts";
|
|
44
|
-
export {
|
|
45
|
+
export {
|
|
46
|
+
MAIL_REF_PREFIX,
|
|
47
|
+
PRESENCE_REF_PREFIX,
|
|
48
|
+
parseForEachRefOutput,
|
|
49
|
+
sanitizeRefComponent,
|
|
50
|
+
} from "./git.ts";
|
|
45
51
|
export { ensureRelayDaemon, relayDaemonStatus, runRelayDaemon } from "./relay-client.ts";
|
|
46
52
|
|
|
47
53
|
/** Re-push at least this often while sessions are live, so peers can treat a
|
|
@@ -139,7 +145,9 @@ export function publishPresence(
|
|
|
139
145
|
const sha = writePresenceCommit(coordRoot, json);
|
|
140
146
|
if (!sha) return { status: "error", error: "commit-tree failed" };
|
|
141
147
|
|
|
142
|
-
const push =
|
|
148
|
+
const push = pushMachineRef(coordRoot, PRESENCE_REF_PREFIX, blob.machine, sha, {
|
|
149
|
+
sync: opts.sync,
|
|
150
|
+
});
|
|
143
151
|
if (!push.ok) return { status: "error", error: push.error ?? "push failed" };
|
|
144
152
|
|
|
145
153
|
// Optimistic on the detached path: a failed background push self-heals at
|
|
@@ -155,7 +163,7 @@ export function publishPresence(
|
|
|
155
163
|
}
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
/** Throttled fetch of peer presence refs from origin. */
|
|
166
|
+
/** Throttled fetch of peer presence and mail refs from origin. */
|
|
159
167
|
export function fetchPresence(
|
|
160
168
|
coordRoot: string,
|
|
161
169
|
opts: { force?: boolean; sync?: boolean } = {},
|
|
@@ -191,7 +199,7 @@ export function fetchPresence(
|
|
|
191
199
|
/* best-effort */
|
|
192
200
|
}
|
|
193
201
|
|
|
194
|
-
const r =
|
|
202
|
+
const r = fetchMachineRefs(coordRoot, { sync: opts.sync });
|
|
195
203
|
if (!r.ok) return { status: "error", error: r.error ?? "fetch failed" };
|
|
196
204
|
return { status: "fetched" };
|
|
197
205
|
} catch (e) {
|
|
@@ -231,7 +239,7 @@ export function readRemoteMachines(
|
|
|
231
239
|
if (prev && Date.parse(prev.published_at) >= publishedMs) return;
|
|
232
240
|
byMachine.set(machine, blob);
|
|
233
241
|
};
|
|
234
|
-
for (const { machine, message } of
|
|
242
|
+
for (const { machine, message } of readMachineRefs(coordRoot, PRESENCE_REF_PREFIX)) {
|
|
235
243
|
try {
|
|
236
244
|
consider(machine, JSON.parse(message) as PresenceBlob);
|
|
237
245
|
} catch {
|