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
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
|
-
import type { WorkflowRecoveryAuthorization } from "../recovery-audit.ts";
|
|
20
19
|
import { git, gitMaybe } from "./git.ts";
|
|
21
20
|
import type { WorkflowLiveness } from "./recovery.ts";
|
|
22
21
|
import type { WorkspaceCleanupAttempt, WorkspaceCleanupReceipt } from "./types.ts";
|
|
@@ -26,7 +25,6 @@ export type ReclaimMode = "salvage" | "discard";
|
|
|
26
25
|
export type WorkflowReclaimRefusalCode =
|
|
27
26
|
| "workflow_reclaim_run_live"
|
|
28
27
|
| "workflow_reclaim_liveness_unknown"
|
|
29
|
-
| "workflow_reclaim_recovery_mismatch"
|
|
30
28
|
| "workflow_reclaim_ownership_unproven"
|
|
31
29
|
| "workflow_reclaim_lease_held";
|
|
32
30
|
|
|
@@ -43,7 +41,8 @@ export class WorkflowReclaimError extends Error {
|
|
|
43
41
|
export interface WorkspaceReclaimOptions {
|
|
44
42
|
mode: ReclaimMode;
|
|
45
43
|
yes?: boolean;
|
|
46
|
-
|
|
44
|
+
/** Admit missing or corrupt liveness evidence; active evidence still refuses. */
|
|
45
|
+
force?: boolean;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
export interface ReclaimLeaseHolder {
|
|
@@ -59,7 +58,6 @@ export interface WorkspaceReclaimPreview {
|
|
|
59
58
|
run_id: string;
|
|
60
59
|
mode: ReclaimMode;
|
|
61
60
|
liveness: WorkflowLiveness;
|
|
62
|
-
evidence_sha256: string;
|
|
63
61
|
ownership: "ok" | "unproven";
|
|
64
62
|
ownership_detail?: string;
|
|
65
63
|
dirty_paths: string[] | null;
|
|
@@ -84,7 +82,7 @@ export interface PrepareReclaimInput {
|
|
|
84
82
|
worktreePath: string;
|
|
85
83
|
mode: ReclaimMode;
|
|
86
84
|
runId: string;
|
|
87
|
-
/**
|
|
85
|
+
/** Recheck peer authority once, immediately before the first destructive step. */
|
|
88
86
|
beforeEffect?: () => void;
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -117,7 +115,6 @@ export function prepareReclaim(input: PrepareReclaimInput): ReclaimPreparation {
|
|
|
117
115
|
// cannot see. Running clean first would leave staged deletions behind.
|
|
118
116
|
input.beforeEffect?.();
|
|
119
117
|
git(input.worktreePath, ["reset", "--hard"]);
|
|
120
|
-
input.beforeEffect?.();
|
|
121
118
|
git(input.worktreePath, ["clean", "-fd"]);
|
|
122
119
|
return { action: "discarded", detail: "uncommitted changes were discarded on request" };
|
|
123
120
|
}
|
|
@@ -138,13 +135,10 @@ export function prepareReclaim(input: PrepareReclaimInput): ReclaimPreparation {
|
|
|
138
135
|
const before = git(input.worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
139
136
|
input.beforeEffect?.();
|
|
140
137
|
git(input.worktreePath, ["add", "-A"]);
|
|
141
|
-
input.beforeEffect?.();
|
|
142
138
|
git(input.worktreePath, ["commit", "--no-verify", "-m", salvageMessage(input.runId)]);
|
|
143
139
|
const commit = git(input.worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
144
140
|
const branch = salvageBranch(input.runId);
|
|
145
|
-
input.beforeEffect?.();
|
|
146
141
|
git(input.worktreePath, ["branch", "--force", branch, commit]);
|
|
147
|
-
input.beforeEffect?.();
|
|
148
142
|
git(input.worktreePath, ["reset", "--hard", before]);
|
|
149
143
|
return {
|
|
150
144
|
action: "salvaged",
|
|
@@ -4,7 +4,7 @@ import { readWorkflowProof } from "../proof.ts";
|
|
|
4
4
|
|
|
5
5
|
export type WorkflowLiveness = "active" | "inactive" | "unknown";
|
|
6
6
|
|
|
7
|
-
/** Corruption must not hide a readable active marker from
|
|
7
|
+
/** Corruption must not hide a readable active marker from a forced reclaim. */
|
|
8
8
|
export function hasActiveWorkflowEvidence(coordRoot: string, runId: string): boolean {
|
|
9
9
|
const path = join(resolve(coordRoot), ".harnery", "workflows", runId, "transcript.jsonl");
|
|
10
10
|
if (!existsSync(path)) return false;
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
stableDigest,
|
|
20
20
|
writeImmutableJson,
|
|
21
21
|
} from "../durable-record.ts";
|
|
22
|
-
import { assertClaimAuthority
|
|
22
|
+
import { assertClaimAuthority } from "./ownership.ts";
|
|
23
23
|
|
|
24
24
|
export { appendWorkflowTranscriptEvent } from "../transcript.ts";
|
|
25
25
|
|
|
@@ -143,10 +143,6 @@ export function readWorkspaceRequest(
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export function writeWorkspaceClaim(coordRoot: string, claim: WorkspaceClaim): boolean {
|
|
146
|
-
return publishWorkspaceClaim(coordRoot, claim, () => writeClaim(coordRoot, claim));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function writeClaim(coordRoot: string, claim: WorkspaceClaim): boolean {
|
|
150
146
|
const path = workspaceClaimPath(coordRoot, claim.provider_id, claim.binding_id);
|
|
151
147
|
try {
|
|
152
148
|
return writeImmutableJson(path, claim);
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Append-only history of admission slot use. The queue files in admission.ts
|
|
2
|
+
// describe only the present: a waiter or holder disappears the moment it is
|
|
3
|
+
// admitted, released, or pruned. This module keeps one JSON line per finished
|
|
4
|
+
// ticket so a later reader can answer how long work queued, behind what, and
|
|
5
|
+
// how long each holder kept its slot.
|
|
6
|
+
//
|
|
7
|
+
// Layout: <history dir>/<resource>.jsonl, rotated to <resource>.1.jsonl once
|
|
8
|
+
// it passes HISTORY_ROTATE_BYTES. Each line is a complete record appended in a
|
|
9
|
+
// single write, so concurrent writers on one machine never interleave.
|
|
10
|
+
//
|
|
11
|
+
// Toolkit tier: this module must not import src/core (layering check).
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
appendFileSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
readdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
statSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { basename, join } from "node:path";
|
|
23
|
+
import { coordEnv } from "./env.ts";
|
|
24
|
+
|
|
25
|
+
export const ADMISSION_HISTORY_SCHEMA_VERSION = 1;
|
|
26
|
+
|
|
27
|
+
/** Rotate a resource's history once it passes this size; one older file is kept. */
|
|
28
|
+
const HISTORY_ROTATE_BYTES = 8 * 1024 * 1024;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How a ticket ended.
|
|
32
|
+
* - `released`: the holder ran and gave the slot back.
|
|
33
|
+
* - `timeout`: no slot freed before the waiter's deadline.
|
|
34
|
+
* - `abandoned`: the process died while waiting or holding; a peer pruned it.
|
|
35
|
+
* - `expired`: the entry outlived the queue TTL and a peer pruned it.
|
|
36
|
+
*/
|
|
37
|
+
export type AdmissionHistoryOutcome = "released" | "timeout" | "abandoned" | "expired";
|
|
38
|
+
|
|
39
|
+
export interface AdmissionHistoryRecord {
|
|
40
|
+
v: typeof ADMISSION_HISTORY_SCHEMA_VERSION;
|
|
41
|
+
resource: string;
|
|
42
|
+
label: string;
|
|
43
|
+
pid: number;
|
|
44
|
+
/** Working directory of the process that queued, when known. */
|
|
45
|
+
cwd?: string;
|
|
46
|
+
capacity?: number;
|
|
47
|
+
outcome: AdmissionHistoryOutcome;
|
|
48
|
+
enqueued_at: string;
|
|
49
|
+
acquired_at?: string;
|
|
50
|
+
/** When the slot was released, the wait gave up, or a peer pruned the entry. */
|
|
51
|
+
ended_at: string;
|
|
52
|
+
/** Time spent in the queue. For an entry pruned while waiting, an upper bound. */
|
|
53
|
+
wait_ms: number;
|
|
54
|
+
/** Time the slot was held. For an entry pruned while holding, an upper bound. */
|
|
55
|
+
hold_ms?: number;
|
|
56
|
+
/** Labels of the holders when this ticket first had to wait. */
|
|
57
|
+
waited_behind?: string[];
|
|
58
|
+
/** Time spent deferring for memory pressure before joining the queue. */
|
|
59
|
+
pressure_wait_ms?: number;
|
|
60
|
+
exit_code?: number | null;
|
|
61
|
+
signal?: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Outcome details a caller can attach when it releases its slot. */
|
|
65
|
+
export interface AdmissionReleaseResult {
|
|
66
|
+
exitCode?: number | null;
|
|
67
|
+
signal?: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Persistent machine-wide history directory used for the default admission
|
|
72
|
+
* queue root. The queues live in tmpdir and correctly vanish at reboot; the
|
|
73
|
+
* history is evidence and must survive one.
|
|
74
|
+
*/
|
|
75
|
+
export function defaultAdmissionHistoryDir(): string {
|
|
76
|
+
const stateHome = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
|
|
77
|
+
return join(stateHome, "harnery", "admission-history");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Directory name of a machine-wide queue root (see admissionBaseDir). */
|
|
81
|
+
const SHARED_QUEUE_DIRNAME = "harnery-admission";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Where history for a given queue root is written, or null when disabled.
|
|
85
|
+
* HARNERY_ADMISSION_HISTORY=off disables recording; HARNERY_ADMISSION_HISTORY_DIR
|
|
86
|
+
* overrides the location. A machine-wide queue root, named `harnery-admission`
|
|
87
|
+
* under any temp directory, records to the persistent state directory. Any
|
|
88
|
+
* other root (an isolated test queue, for example) records beside itself as
|
|
89
|
+
* `<root>-history`, so isolated queues never write into the
|
|
90
|
+
* machine-wide history and the history directory is never listed as a resource.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveAdmissionHistoryDir(queueDir: string): string | null {
|
|
93
|
+
const toggle = coordEnv("ADMISSION_HISTORY")?.trim().toLowerCase();
|
|
94
|
+
if (toggle === "off" || toggle === "0" || toggle === "false") return null;
|
|
95
|
+
const override = coordEnv("ADMISSION_HISTORY_DIR");
|
|
96
|
+
if (override) return override;
|
|
97
|
+
return basename(queueDir) === SHARED_QUEUE_DIRNAME
|
|
98
|
+
? defaultAdmissionHistoryDir()
|
|
99
|
+
: `${queueDir}-history`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function historyFile(dir: string, resource: string, generation = 0): string {
|
|
103
|
+
return join(dir, generation === 0 ? `${resource}.jsonl` : `${resource}.${generation}.jsonl`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Append one record. History is diagnostic: a write failure never fails the
|
|
108
|
+
* caller's admission, so errors are swallowed.
|
|
109
|
+
*/
|
|
110
|
+
export function appendAdmissionHistory(dir: string | null, record: AdmissionHistoryRecord): void {
|
|
111
|
+
if (!dir) return;
|
|
112
|
+
try {
|
|
113
|
+
mkdirSync(dir, { recursive: true });
|
|
114
|
+
const path = historyFile(dir, record.resource);
|
|
115
|
+
try {
|
|
116
|
+
if (statSync(path).size > HISTORY_ROTATE_BYTES) {
|
|
117
|
+
renameSync(path, historyFile(dir, record.resource, 1));
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// no file yet, or a peer rotated it first
|
|
121
|
+
}
|
|
122
|
+
appendFileSync(path, `${JSON.stringify(record)}\n`);
|
|
123
|
+
} catch {
|
|
124
|
+
// diagnostic only
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Resources with recorded history in a history directory. */
|
|
129
|
+
export function listAdmissionHistoryResources(dir: string): string[] {
|
|
130
|
+
try {
|
|
131
|
+
const names = new Set<string>();
|
|
132
|
+
for (const name of readdirSync(dir)) {
|
|
133
|
+
const match = /^(.+?)(?:\.1)?\.jsonl$/.exec(name);
|
|
134
|
+
if (match?.[1]) names.add(match[1]);
|
|
135
|
+
}
|
|
136
|
+
return [...names].sort();
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseRecord(line: string): AdmissionHistoryRecord | undefined {
|
|
143
|
+
try {
|
|
144
|
+
const parsed: unknown = JSON.parse(line);
|
|
145
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
146
|
+
const record = parsed as Partial<AdmissionHistoryRecord>;
|
|
147
|
+
if (
|
|
148
|
+
typeof record.resource !== "string" ||
|
|
149
|
+
typeof record.outcome !== "string" ||
|
|
150
|
+
typeof record.enqueued_at !== "string" ||
|
|
151
|
+
typeof record.ended_at !== "string" ||
|
|
152
|
+
typeof record.wait_ms !== "number"
|
|
153
|
+
) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return record as AdmissionHistoryRecord;
|
|
157
|
+
} catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface AdmissionHistoryQuery {
|
|
163
|
+
resource?: string;
|
|
164
|
+
/** Keep records that ended at or after this epoch-ms. */
|
|
165
|
+
sinceMs?: number;
|
|
166
|
+
/** Keep records whose label contains this text (case-insensitive). */
|
|
167
|
+
labelContains?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Read history records, oldest first, across the current and rotated files. */
|
|
171
|
+
export function readAdmissionHistory(
|
|
172
|
+
dir: string | null,
|
|
173
|
+
query: AdmissionHistoryQuery = {},
|
|
174
|
+
): AdmissionHistoryRecord[] {
|
|
175
|
+
if (!dir) return [];
|
|
176
|
+
const resources =
|
|
177
|
+
query.resource !== undefined ? [query.resource] : listAdmissionHistoryResources(dir);
|
|
178
|
+
const needle = query.labelContains?.toLowerCase();
|
|
179
|
+
const records: AdmissionHistoryRecord[] = [];
|
|
180
|
+
for (const resource of resources) {
|
|
181
|
+
for (const path of [historyFile(dir, resource, 1), historyFile(dir, resource)]) {
|
|
182
|
+
let text: string;
|
|
183
|
+
try {
|
|
184
|
+
text = readFileSync(path, "utf8");
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
for (const line of text.split("\n")) {
|
|
189
|
+
if (!line) continue;
|
|
190
|
+
const record = parseRecord(line);
|
|
191
|
+
if (!record) continue;
|
|
192
|
+
if (query.sinceMs !== undefined && Date.parse(record.ended_at) < query.sinceMs) continue;
|
|
193
|
+
if (needle && !record.label.toLowerCase().includes(needle)) continue;
|
|
194
|
+
records.push(record);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return records.sort((a, b) => Date.parse(a.ended_at) - Date.parse(b.ended_at));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface AdmissionHistoryGroup {
|
|
202
|
+
resource: string;
|
|
203
|
+
label: string;
|
|
204
|
+
count: number;
|
|
205
|
+
/** Records that waited at least one second for a slot. */
|
|
206
|
+
queued: number;
|
|
207
|
+
wait_ms: { p50: number; p90: number; max: number; total: number };
|
|
208
|
+
/** Hold statistics over released records only. */
|
|
209
|
+
hold_ms: { p50: number; p90: number; max: number } | null;
|
|
210
|
+
outcomes: Partial<Record<AdmissionHistoryOutcome, number>>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function percentile(sorted: number[], p: number): number {
|
|
214
|
+
if (sorted.length === 0) return 0;
|
|
215
|
+
return sorted[Math.min(sorted.length - 1, Math.ceil(p * sorted.length) - 1)] ?? 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const QUEUED_THRESHOLD_MS = 1_000;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Group records by resource and label, most total wait first, so the work
|
|
222
|
+
* that spends the longest queueing leads the report.
|
|
223
|
+
*/
|
|
224
|
+
export function summarizeAdmissionHistory(
|
|
225
|
+
records: AdmissionHistoryRecord[],
|
|
226
|
+
): AdmissionHistoryGroup[] {
|
|
227
|
+
const groups = new Map<string, AdmissionHistoryRecord[]>();
|
|
228
|
+
for (const record of records) {
|
|
229
|
+
const key = `${record.resource}\u0000${record.label}`;
|
|
230
|
+
const bucket = groups.get(key);
|
|
231
|
+
if (bucket) bucket.push(record);
|
|
232
|
+
else groups.set(key, [record]);
|
|
233
|
+
}
|
|
234
|
+
const summaries: AdmissionHistoryGroup[] = [];
|
|
235
|
+
for (const bucket of groups.values()) {
|
|
236
|
+
const first = bucket[0] as AdmissionHistoryRecord;
|
|
237
|
+
const waits = bucket.map((record) => record.wait_ms).sort((a, b) => a - b);
|
|
238
|
+
const holds = bucket
|
|
239
|
+
.filter((record) => record.outcome === "released" && typeof record.hold_ms === "number")
|
|
240
|
+
.map((record) => record.hold_ms as number)
|
|
241
|
+
.sort((a, b) => a - b);
|
|
242
|
+
const outcomes: Partial<Record<AdmissionHistoryOutcome, number>> = {};
|
|
243
|
+
for (const record of bucket) outcomes[record.outcome] = (outcomes[record.outcome] ?? 0) + 1;
|
|
244
|
+
summaries.push({
|
|
245
|
+
resource: first.resource,
|
|
246
|
+
label: first.label,
|
|
247
|
+
count: bucket.length,
|
|
248
|
+
queued: waits.filter((wait) => wait >= QUEUED_THRESHOLD_MS).length,
|
|
249
|
+
wait_ms: {
|
|
250
|
+
p50: percentile(waits, 0.5),
|
|
251
|
+
p90: percentile(waits, 0.9),
|
|
252
|
+
max: waits[waits.length - 1] ?? 0,
|
|
253
|
+
total: waits.reduce((sum, wait) => sum + wait, 0),
|
|
254
|
+
},
|
|
255
|
+
hold_ms:
|
|
256
|
+
holds.length > 0
|
|
257
|
+
? {
|
|
258
|
+
p50: percentile(holds, 0.5),
|
|
259
|
+
p90: percentile(holds, 0.9),
|
|
260
|
+
max: holds[holds.length - 1] ?? 0,
|
|
261
|
+
}
|
|
262
|
+
: null,
|
|
263
|
+
outcomes,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return summaries.sort((a, b) => b.wait_ms.total - a.wait_ms.total);
|
|
267
|
+
}
|