harnery 0.45.2 → 0.46.1
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/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/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +36 -16
- package/dist/core/agents/rules/stop-hook.d.ts +6 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +20 -1
- 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/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +63 -0
- 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/validate.d.ts.map +1 -1
- package/dist/core/events/v3/validate.js +36 -1
- 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 +103 -5
- 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 +0 -23
- package/dist/core/hooks/resolve/transcript.d.ts +12 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +54 -0
- 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/semantic/contract.d.ts +5 -5
- 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/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/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/rules/claim-conflict.ts +50 -18
- package/src/core/agents/rules/stop-hook.ts +28 -1
- 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/config.ts +85 -0
- 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/validate.ts +42 -1
- package/src/core/hooks/adapter/native-parent.ts +109 -0
- package/src/core/hooks/cli.ts +110 -4
- package/src/core/hooks/resolve/owner.ts +0 -23
- package/src/core/hooks/resolve/transcript.ts +61 -0
- 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/storage/modes.ts +6 -0
- package/src/core/storage/segments.ts +5 -4
- 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
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CoordinationGenerationViewV3,
|
|
3
|
+
CoordinationViewV3,
|
|
4
|
+
} from "../../events/v3/coordination-view.ts";
|
|
5
|
+
import { readCoordinationViewV3 } from "../../events/v3/coordination-view.ts";
|
|
6
|
+
import { liveInstanceIdV3, nativeInstanceIdV3 } from "../../events/v3/live-route-observer.ts";
|
|
7
|
+
import {
|
|
8
|
+
type NativeParentObservation,
|
|
9
|
+
type NativeParentRequest,
|
|
10
|
+
readNativeParentReference,
|
|
11
|
+
} from "../../hooks/adapter/native-parent.ts";
|
|
12
|
+
import { resolveName } from "./names.ts";
|
|
13
|
+
import type { AgentParentReference } from "./parent-reference-contract.ts";
|
|
14
|
+
|
|
15
|
+
export type { AgentParentReference } from "./parent-reference-contract.ts";
|
|
16
|
+
|
|
17
|
+
export interface AgentParentSubject {
|
|
18
|
+
instance_id: string;
|
|
19
|
+
generation_id?: string;
|
|
20
|
+
v3_generation_id?: string;
|
|
21
|
+
platform?: string;
|
|
22
|
+
native_session_id?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentParentReferenceOptions {
|
|
26
|
+
view?: CoordinationViewV3;
|
|
27
|
+
readNativeParent?: (request: NativeParentRequest) => NativeParentObservation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Display/recipient evidence only. Never use native evidence as a claim or lifecycle waiver. */
|
|
31
|
+
export function resolveAgentParentReference(
|
|
32
|
+
coordRoot: string,
|
|
33
|
+
child: AgentParentSubject,
|
|
34
|
+
options: AgentParentReferenceOptions = {},
|
|
35
|
+
): AgentParentReference {
|
|
36
|
+
try {
|
|
37
|
+
const view = options.view ?? readCoordinationViewV3(coordRoot);
|
|
38
|
+
if (!view.source_complete || !view.authority_safe) return unknown("parent_authority_unsafe");
|
|
39
|
+
if (
|
|
40
|
+
child.generation_id &&
|
|
41
|
+
child.v3_generation_id &&
|
|
42
|
+
child.generation_id !== child.v3_generation_id
|
|
43
|
+
) {
|
|
44
|
+
return unknown("child_generation_mismatch");
|
|
45
|
+
}
|
|
46
|
+
const instance = liveInstanceIdV3(child.instance_id);
|
|
47
|
+
const generationId = child.generation_id ?? child.v3_generation_id;
|
|
48
|
+
const generations = [
|
|
49
|
+
...Object.values(view.instances),
|
|
50
|
+
...Object.values(view.terminal_generations),
|
|
51
|
+
];
|
|
52
|
+
const generation = generationId
|
|
53
|
+
? generations.find(
|
|
54
|
+
(row) => row.generation_id === generationId && row.instance_id === instance,
|
|
55
|
+
)
|
|
56
|
+
: view.instances[instance];
|
|
57
|
+
if (!generation) return unknown("child_generation_unavailable");
|
|
58
|
+
if (!safe(view, generation)) return unknown("child_authority_unsafe");
|
|
59
|
+
const canonical = generation.parent_generation_id
|
|
60
|
+
? generations.find((row) => row.generation_id === generation.parent_generation_id)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (generation.parent_generation_id && !canonical)
|
|
63
|
+
return unknown("parent_generation_unavailable");
|
|
64
|
+
if (canonical && cyclic(generations, generation)) {
|
|
65
|
+
return { state: "conflict", reason: "parent_lineage_cycle" };
|
|
66
|
+
}
|
|
67
|
+
const adapter = generation.runtime_attestation.adapter;
|
|
68
|
+
const platform = adapter.state === "observed" ? adapter.value.id : child.platform;
|
|
69
|
+
const nativeId = child.native_session_id ?? nativeInstanceIdV3(instance);
|
|
70
|
+
const native =
|
|
71
|
+
platform === "codex" && liveInstanceIdV3(nativeId) === instance
|
|
72
|
+
? (options.readNativeParent ?? readNativeParentReference)({
|
|
73
|
+
adapter: platform,
|
|
74
|
+
native_session_id: nativeId,
|
|
75
|
+
})
|
|
76
|
+
: ({ state: "unknown", reason: "native_child_identity_unavailable" } as const);
|
|
77
|
+
if (canonical) {
|
|
78
|
+
if (
|
|
79
|
+
native.state === "none" ||
|
|
80
|
+
(native.state === "observed" &&
|
|
81
|
+
liveInstanceIdV3(native.parent_session_id) !== canonical.instance_id)
|
|
82
|
+
) {
|
|
83
|
+
return { state: "conflict", reason: "parent_sources_disagree" };
|
|
84
|
+
}
|
|
85
|
+
return reference(coordRoot, view, canonical.instance_id, canonical, "v3-session-link");
|
|
86
|
+
}
|
|
87
|
+
if (native.state === "observed") {
|
|
88
|
+
const parentInstance = liveInstanceIdV3(native.parent_session_id);
|
|
89
|
+
if (parentInstance === instance)
|
|
90
|
+
return { state: "conflict", reason: "parent_self_reference" };
|
|
91
|
+
const parent = recipientGeneration(view, parentInstance);
|
|
92
|
+
if (parent && cyclic(generations, generation, parent)) {
|
|
93
|
+
return { state: "conflict", reason: "parent_lineage_cycle" };
|
|
94
|
+
}
|
|
95
|
+
return reference(
|
|
96
|
+
coordRoot,
|
|
97
|
+
view,
|
|
98
|
+
parentInstance,
|
|
99
|
+
parent,
|
|
100
|
+
"native-session-metadata",
|
|
101
|
+
native.source_witness,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return { state: native.state, reason: native.reason };
|
|
105
|
+
} catch {
|
|
106
|
+
return unknown("parent_reference_unavailable");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function reference(
|
|
111
|
+
root: string,
|
|
112
|
+
view: CoordinationViewV3,
|
|
113
|
+
instance: string,
|
|
114
|
+
parent: CoordinationGenerationViewV3 | undefined,
|
|
115
|
+
source: "v3-session-link" | "native-session-metadata",
|
|
116
|
+
witness?: string,
|
|
117
|
+
): AgentParentReference {
|
|
118
|
+
const native = nativeInstanceIdV3(instance);
|
|
119
|
+
const name = resolveName(root, native)?.name ?? null;
|
|
120
|
+
const recipient = recipientGeneration(view, instance);
|
|
121
|
+
return {
|
|
122
|
+
state: "verified",
|
|
123
|
+
relationship: source === "v3-session-link" ? "delegated-child" : "native-child",
|
|
124
|
+
parent_instance_id: native,
|
|
125
|
+
parent_v3_instance_id: instance,
|
|
126
|
+
parent_generation_id: parent?.generation_id ?? null,
|
|
127
|
+
parent_session_id: parent?.session_id ?? null,
|
|
128
|
+
parent_name: name,
|
|
129
|
+
source,
|
|
130
|
+
...(witness ? { source_witness: witness } : {}),
|
|
131
|
+
addressable: Boolean(
|
|
132
|
+
parent && recipient?.generation_id === parent.generation_id && safe(view, parent) && name,
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function recipientGeneration(view: CoordinationViewV3, instance: string) {
|
|
138
|
+
return (
|
|
139
|
+
view.instances[instance] ??
|
|
140
|
+
Object.values(view.terminal_generations)
|
|
141
|
+
.filter((row) => row.instance_id === instance)
|
|
142
|
+
.sort(
|
|
143
|
+
(a, b) =>
|
|
144
|
+
b.source_position.segment_ordinal - a.source_position.segment_ordinal ||
|
|
145
|
+
b.source_position.byte_offset - a.source_position.byte_offset,
|
|
146
|
+
)[0]
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function safe(view: CoordinationViewV3, generation: CoordinationGenerationViewV3): boolean {
|
|
151
|
+
return !(view.diagnostics_by_generation[generation.generation_id] ?? []).some(
|
|
152
|
+
(d) => d.authority_blocking,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function cyclic(
|
|
157
|
+
generations: CoordinationGenerationViewV3[],
|
|
158
|
+
child: CoordinationGenerationViewV3,
|
|
159
|
+
first?: CoordinationGenerationViewV3,
|
|
160
|
+
): boolean {
|
|
161
|
+
const seen = new Set([child.generation_id]);
|
|
162
|
+
let cursor = first ?? child;
|
|
163
|
+
for (let depth = 0; depth <= generations.length; depth++) {
|
|
164
|
+
if (cursor !== child) {
|
|
165
|
+
if (seen.has(cursor.generation_id) || cursor.instance_id === child.instance_id) return true;
|
|
166
|
+
seen.add(cursor.generation_id);
|
|
167
|
+
}
|
|
168
|
+
if (!cursor.parent_generation_id) return false;
|
|
169
|
+
const next = generations.find((g) => g.generation_id === cursor.parent_generation_id);
|
|
170
|
+
if (!next) return false;
|
|
171
|
+
if (seen.has(next.generation_id)) return true;
|
|
172
|
+
cursor = next;
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function unknown(reason: string): AgentParentReference {
|
|
178
|
+
return { state: "unknown", reason };
|
|
179
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -92,6 +92,8 @@ interface HarneryConfig {
|
|
|
92
92
|
requireGitFinalization?: boolean;
|
|
93
93
|
finalizationRoots?: AgentFinalizationRoot[];
|
|
94
94
|
};
|
|
95
|
+
/** Admission resource defaults and the heavy-command guard. Project config only. */
|
|
96
|
+
admission?: unknown;
|
|
95
97
|
/** Host-owned agent instruction settings. */
|
|
96
98
|
instructions?: {
|
|
97
99
|
hostAddendumFile?: unknown;
|
|
@@ -633,6 +635,89 @@ export function ripgrepAutoInstall(coordRoot?: string | null): boolean {
|
|
|
633
635
|
return readConfig(root).tools?.ripgrep?.autoInstall === true;
|
|
634
636
|
}
|
|
635
637
|
|
|
638
|
+
/** Wait before queueing while Linux memory pressure is high (PSI `full avg10`). */
|
|
639
|
+
export interface AdmissionPressureConfig {
|
|
640
|
+
psi_full_avg10: number;
|
|
641
|
+
max_wait_seconds: number;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Per-resource defaults for `admission run`; command-line flags win. */
|
|
645
|
+
export interface AdmissionResourceConfig {
|
|
646
|
+
capacity?: number;
|
|
647
|
+
timeout_minutes?: number;
|
|
648
|
+
defer_on_pressure?: AdmissionPressureConfig;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export interface AdmissionProjectConfig {
|
|
652
|
+
resources: Record<string, AdmissionResourceConfig>;
|
|
653
|
+
/** Present when the agent hook should wrap heavy shell commands in a slot. */
|
|
654
|
+
guard?: { resource: string };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function positiveNumber(value: unknown, max: number): number | undefined {
|
|
658
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= max
|
|
659
|
+
? value
|
|
660
|
+
: undefined;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Admission settings from `.harnery/config.jsonc`:
|
|
665
|
+
*
|
|
666
|
+
* ```jsonc
|
|
667
|
+
* "admission": {
|
|
668
|
+
* "resources": {
|
|
669
|
+
* "build": {
|
|
670
|
+
* "capacity": 2,
|
|
671
|
+
* "timeout_minutes": 30,
|
|
672
|
+
* "defer_on_pressure": { "psi_full_avg10": 40, "max_wait_seconds": 300 }
|
|
673
|
+
* }
|
|
674
|
+
* },
|
|
675
|
+
* "guard": { "resource": "build" }
|
|
676
|
+
* }
|
|
677
|
+
* ```
|
|
678
|
+
*
|
|
679
|
+
* Project config only, like other machine-shaping policy. Malformed values are
|
|
680
|
+
* dropped one by one rather than rejecting the whole block. Undefined when the
|
|
681
|
+
* project declares no admission settings.
|
|
682
|
+
*/
|
|
683
|
+
export function readAdmissionConfig(coordRoot?: string | null): AdmissionProjectConfig | undefined {
|
|
684
|
+
const root = coordRoot ?? findCoordRoot();
|
|
685
|
+
if (!root) return undefined;
|
|
686
|
+
const raw = readProjectConfig(root).admission;
|
|
687
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
688
|
+
const record = raw as { resources?: unknown; guard?: unknown };
|
|
689
|
+
const resources: Record<string, AdmissionResourceConfig> = {};
|
|
690
|
+
if (
|
|
691
|
+
record.resources &&
|
|
692
|
+
typeof record.resources === "object" &&
|
|
693
|
+
!Array.isArray(record.resources)
|
|
694
|
+
) {
|
|
695
|
+
for (const [name, value] of Object.entries(record.resources as Record<string, unknown>)) {
|
|
696
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
697
|
+
const entry = value as Record<string, unknown>;
|
|
698
|
+
const capacity = positiveNumber(entry.capacity, 32);
|
|
699
|
+
const timeout = positiveNumber(entry.timeout_minutes, 1440);
|
|
700
|
+
const pressure = entry.defer_on_pressure as Record<string, unknown> | undefined;
|
|
701
|
+
const threshold = positiveNumber(pressure?.psi_full_avg10, 100);
|
|
702
|
+
const maxWait = positiveNumber(pressure?.max_wait_seconds, 86_400);
|
|
703
|
+
resources[name] = {
|
|
704
|
+
...(capacity !== undefined && Number.isInteger(capacity) ? { capacity } : {}),
|
|
705
|
+
...(timeout !== undefined && Number.isInteger(timeout) ? { timeout_minutes: timeout } : {}),
|
|
706
|
+
...(threshold !== undefined && maxWait !== undefined
|
|
707
|
+
? { defer_on_pressure: { psi_full_avg10: threshold, max_wait_seconds: maxWait } }
|
|
708
|
+
: {}),
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const guard = record.guard as { resource?: unknown } | undefined;
|
|
713
|
+
return {
|
|
714
|
+
resources,
|
|
715
|
+
...(guard && typeof guard.resource === "string" && guard.resource
|
|
716
|
+
? { guard: { resource: guard.resource } }
|
|
717
|
+
: {}),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
636
721
|
/**
|
|
637
722
|
* Whether this repo pins workflow runs to subscription billing:
|
|
638
723
|
* `.harnery/config.jsonc` `{ "workflow": { "subscriptionOnly": true } }`.
|
|
@@ -1,15 +1,61 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { PERSONAL_DIR_MODE, PERSONAL_FILE_MODE } from "../storage/modes.ts";
|
|
2
6
|
|
|
3
7
|
const HOME_PATH =
|
|
4
8
|
/(?:[A-Za-z]:[\\/]Users[\\/](?:[^\\/\r\n\0:"<>|]+(?=[\\/])|[^\\/\s\0:]+)|\/home\/[^/\s\0:]+|\/Users\/[^/\s\0:]+)/g;
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
const KEY_PATTERN = /^[0-9a-f]{64}$/;
|
|
11
|
+
let cachedKey: Buffer | undefined;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Per-install secret for home-path pseudonyms. An unkeyed digest would let
|
|
15
|
+
* anyone holding a bundle recover the user name by hashing guesses, so the
|
|
16
|
+
* key stays in local state and never enters a bundle. Sanitized values carry
|
|
17
|
+
* the pseudonym itself, so replay elsewhere needs no key.
|
|
18
|
+
*/
|
|
19
|
+
function identityKeyPath(): string {
|
|
20
|
+
const xdg = process.env.XDG_STATE_HOME;
|
|
21
|
+
const base = xdg?.trim() ? xdg : join(homedir(), ".local", "state");
|
|
22
|
+
return join(base, "harnery", "diagnostic-identity.key");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readKey(path: string): Buffer | undefined {
|
|
26
|
+
try {
|
|
27
|
+
const text = readFileSync(path, "utf8").trim();
|
|
28
|
+
return KEY_PATTERN.test(text) ? Buffer.from(text, "hex") : undefined;
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function identityKey(): Buffer {
|
|
35
|
+
if (cachedKey) return cachedKey;
|
|
36
|
+
const path = identityKeyPath();
|
|
37
|
+
let key = readKey(path);
|
|
38
|
+
if (!key) {
|
|
39
|
+
const fresh = randomBytes(32);
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true, mode: PERSONAL_DIR_MODE });
|
|
42
|
+
writeFileSync(path, `${fresh.toString("hex")}\n`, { flag: "wx", mode: PERSONAL_FILE_MODE });
|
|
43
|
+
chmodSync(path, PERSONAL_FILE_MODE);
|
|
44
|
+
key = fresh;
|
|
45
|
+
} catch {
|
|
46
|
+
// A concurrent writer won the race, or state is unwritable. Prefer the
|
|
47
|
+
// stored key; otherwise stay private with a process-local key.
|
|
48
|
+
key = readKey(path) ?? fresh;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
cachedKey = key;
|
|
52
|
+
return key;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One stable, non-reversible representation for private home paths in values and identity inputs. */
|
|
7
56
|
export function canonicalizeDiagnosticIdentity(value: string): string {
|
|
8
57
|
return value.replace(HOME_PATH, (home) => {
|
|
9
|
-
const digest =
|
|
10
|
-
.update("harnery-diagnostic-home-v1\0")
|
|
11
|
-
.update(home)
|
|
12
|
-
.digest("hex");
|
|
58
|
+
const digest = createHmac("sha256", identityKey()).update(home).digest("hex");
|
|
13
59
|
return `[REDACTED:home:${digest}]`;
|
|
14
60
|
});
|
|
15
61
|
}
|
|
@@ -104,10 +104,19 @@ export function normalizeNativeIdV3(
|
|
|
104
104
|
return `hid_${digest}`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* NFC leaves ASCII unchanged, so ASCII strings and key sets skip normalization
|
|
109
|
+
* and the collision map. Every reader re-serializes each ledger frame to prove
|
|
110
|
+
* it is canonical, which makes this the hottest function on a cold read.
|
|
111
|
+
*/
|
|
112
|
+
const NON_ASCII = /[\u0080-\uffff]/;
|
|
113
|
+
|
|
107
114
|
function serialize(value: unknown, ancestors: Set<object>): string {
|
|
108
115
|
if (value === null) return "null";
|
|
109
116
|
if (typeof value === "boolean") return value ? "true" : "false";
|
|
110
|
-
if (typeof value === "string")
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
return JSON.stringify(NON_ASCII.test(value) ? value.normalize("NFC") : value);
|
|
119
|
+
}
|
|
111
120
|
if (typeof value === "number") {
|
|
112
121
|
if (!Number.isFinite(value)) throw new Error("canonical JSON rejects non-finite numbers");
|
|
113
122
|
return JSON.stringify(Object.is(value, -0) ? 0 : value);
|
|
@@ -119,29 +128,53 @@ function serialize(value: unknown, ancestors: Set<object>): string {
|
|
|
119
128
|
ancestors.add(value);
|
|
120
129
|
try {
|
|
121
130
|
if (Array.isArray(value)) {
|
|
122
|
-
|
|
131
|
+
let out = "[";
|
|
132
|
+
for (let index = 0; index < value.length; index++) {
|
|
133
|
+
if (index > 0) out += ",";
|
|
134
|
+
out += serialize(value[index], ancestors);
|
|
135
|
+
}
|
|
136
|
+
return `${out}]`;
|
|
123
137
|
}
|
|
124
138
|
const prototype = Object.getPrototypeOf(value);
|
|
125
139
|
if (prototype !== Object.prototype && prototype !== null) {
|
|
126
140
|
throw new Error("canonical JSON accepts only plain objects");
|
|
127
141
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
throw new Error("canonical JSON key collision after Unicode normalization");
|
|
133
|
-
}
|
|
134
|
-
normalized.set(normalizedKey, item);
|
|
142
|
+
const object = value as Record<string, unknown>;
|
|
143
|
+
const keys = Object.keys(object);
|
|
144
|
+
if (keys.some((key) => NON_ASCII.test(key))) {
|
|
145
|
+
return serializeNormalizedObject(object, ancestors);
|
|
135
146
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
keys.sort(compareUtf16);
|
|
148
|
+
let out = "{";
|
|
149
|
+
for (let index = 0; index < keys.length; index++) {
|
|
150
|
+
const key = keys[index] as string;
|
|
151
|
+
if (index > 0) out += ",";
|
|
152
|
+
out += `${JSON.stringify(key)}:${serialize(object[key], ancestors)}`;
|
|
153
|
+
}
|
|
154
|
+
return `${out}}`;
|
|
140
155
|
} finally {
|
|
141
156
|
ancestors.delete(value);
|
|
142
157
|
}
|
|
143
158
|
}
|
|
144
159
|
|
|
160
|
+
function serializeNormalizedObject(
|
|
161
|
+
object: Record<string, unknown>,
|
|
162
|
+
ancestors: Set<object>,
|
|
163
|
+
): string {
|
|
164
|
+
const normalized = new Map<string, unknown>();
|
|
165
|
+
for (const [key, item] of Object.entries(object)) {
|
|
166
|
+
const normalizedKey = key.normalize("NFC");
|
|
167
|
+
if (normalized.has(normalizedKey)) {
|
|
168
|
+
throw new Error("canonical JSON key collision after Unicode normalization");
|
|
169
|
+
}
|
|
170
|
+
normalized.set(normalizedKey, item);
|
|
171
|
+
}
|
|
172
|
+
const keys = [...normalized.keys()].sort(compareUtf16);
|
|
173
|
+
return `{${keys
|
|
174
|
+
.map((key) => `${JSON.stringify(key)}:${serialize(normalized.get(key), ancestors)}`)
|
|
175
|
+
.join(",")}}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
145
178
|
function compareUtf16(left: string, right: string): number {
|
|
146
179
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
147
180
|
}
|
|
@@ -247,7 +247,9 @@ export {
|
|
|
247
247
|
type DrainHookIntakeSpoolV3Result,
|
|
248
248
|
drainHookIntakeSpoolV3,
|
|
249
249
|
type HookProducerStateRecordV3,
|
|
250
|
+
type HookProducerStateScanV3,
|
|
250
251
|
type HookProducerStateV3,
|
|
252
|
+
type InvalidHookProducerStateFileV3,
|
|
251
253
|
listHookProducerStateRecordsV3,
|
|
252
254
|
type RecordApprovedSessionEndV3Input,
|
|
253
255
|
type RecordApprovedSessionEndV3Result,
|
|
@@ -256,6 +258,7 @@ export {
|
|
|
256
258
|
readHookProducerStateV3,
|
|
257
259
|
recordApprovedSessionEndV3,
|
|
258
260
|
recordHookSignalV3,
|
|
261
|
+
scanHookProducerStateRecordsV3,
|
|
259
262
|
} from "./producers/recorder.ts";
|
|
260
263
|
export {
|
|
261
264
|
type ClaimProjectionV3,
|