forge-workflow 0.1.0-beta.6 → 0.1.0-beta.7
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/CHANGELOG.md +12 -0
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/RELEASE.md +3 -3
- package/lib/commands/merge.js +2 -2
- package/lib/commands/push.js +13 -4
- package/lib/commands/shepherd.js +1 -1
- package/lib/commands/validate.js +13 -1
- package/lib/kernel/migrations.js +1 -1
- package/lib/kernel/sqlite-driver.js +1 -1
- package/lib/memory/router.js +1 -1
- package/lib/memory/usage-evidence.js +1 -1
- package/lib/pr-monitor/flow-monitor.js +2 -2
- package/lib/pr-monitor/reconcile-executor.js +1 -1
- package/lib/project-memory.js +1 -1
- package/lib/validation-receipt.js +190 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +9 -1
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/test.js +1 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_LIMITS = Object.freeze({ maxBytes: 8_192, maxDepth: 8, maxNodes: 128 });
|
|
4
|
+
const LIMIT_CEILINGS = DEFAULT_LIMITS;
|
|
5
|
+
const NODE_STATUSES = new Set(["PASS", "FAIL", "INCOMPLETE"]);
|
|
6
|
+
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
9
|
+
const prototype = Object.getPrototypeOf(value);
|
|
10
|
+
return prototype === Object.prototype || prototype === null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function safeFailure(code, details = {}, skipped = []) {
|
|
14
|
+
return {
|
|
15
|
+
status: "INCOMPLETE",
|
|
16
|
+
invoked: [],
|
|
17
|
+
skipped,
|
|
18
|
+
evidence: [],
|
|
19
|
+
error: { code, ...details },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function boundedClone(value, limits) {
|
|
24
|
+
let nodes = 0;
|
|
25
|
+
|
|
26
|
+
function visit(current, depth) {
|
|
27
|
+
nodes += 1;
|
|
28
|
+
if (nodes > limits.maxNodes || depth > limits.maxDepth) {
|
|
29
|
+
throw new RangeError("bounded value exceeds structural limits");
|
|
30
|
+
}
|
|
31
|
+
if (current === null || typeof current === "string" || typeof current === "boolean") {
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
34
|
+
if (typeof current === "number" && Number.isFinite(current)) return current;
|
|
35
|
+
if (Array.isArray(current)) return current.map((item) => visit(item, depth + 1));
|
|
36
|
+
if (!isPlainObject(current)) throw new TypeError("bounded value must contain JSON data only");
|
|
37
|
+
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const key of Object.keys(current)) {
|
|
40
|
+
result[key] = visit(current[key], depth + 1);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const clone = visit(value, 0);
|
|
46
|
+
if (Buffer.byteLength(JSON.stringify(clone), "utf8") > limits.maxBytes) {
|
|
47
|
+
throw new RangeError("bounded value exceeds byte limit");
|
|
48
|
+
}
|
|
49
|
+
return clone;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function selectKeys(source, keys) {
|
|
53
|
+
const selected = {};
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (Object.hasOwn(source, key)) selected[key] = source[key];
|
|
56
|
+
}
|
|
57
|
+
return selected;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class SkillRuntime {
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
const optionsValid = isPlainObject(options);
|
|
63
|
+
if (!optionsValid) options = {};
|
|
64
|
+
this.metadata = options.metadata;
|
|
65
|
+
this.handlers = Object.assign(Object.create(null), options.handlers || {});
|
|
66
|
+
this.capabilities = new Set(options.capabilities || []);
|
|
67
|
+
this.limits = { ...DEFAULT_LIMITS, ...(options.limits || {}) };
|
|
68
|
+
this.active = false;
|
|
69
|
+
this.nodes = new Map();
|
|
70
|
+
this.optionsError = optionsValid ? null : { code: "INVALID_OPTIONS", details: {} };
|
|
71
|
+
this.limitError = this._validateLimits(options.limits);
|
|
72
|
+
this.metadataError = this._indexMetadata();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async invoke(request = {}) {
|
|
76
|
+
if (this.active) return safeFailure("RECURSIVE_INVOCATION");
|
|
77
|
+
if (!isPlainObject(request)) return safeFailure("INVALID_REQUEST");
|
|
78
|
+
if (this.optionsError) return safeFailure(this.optionsError.code, this.optionsError.details);
|
|
79
|
+
if (this.limitError) return safeFailure("INVALID_LIMITS", this.limitError);
|
|
80
|
+
if (this.metadataError) return this._failure(this.metadataError.code, this.metadataError.details);
|
|
81
|
+
|
|
82
|
+
const requestedNodes = request.nodes;
|
|
83
|
+
if (!Array.isArray(requestedNodes) || requestedNodes.length === 0) {
|
|
84
|
+
return safeFailure("INVALID_REQUEST");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const requestedSet = new Set();
|
|
88
|
+
for (const nodeId of requestedNodes) {
|
|
89
|
+
if (requestedSet.has(nodeId)) {
|
|
90
|
+
return this._failure("DUPLICATE_INVOCATION", { nodeId });
|
|
91
|
+
}
|
|
92
|
+
requestedSet.add(nodeId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const resolution = this._resolve(requestedNodes);
|
|
96
|
+
if (resolution.error) {
|
|
97
|
+
return this._failure(resolution.error.code, resolution.error.details);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const skipped = [...this.nodes.keys()]
|
|
101
|
+
.filter((nodeId) => !resolution.required.has(nodeId))
|
|
102
|
+
.map((nodeId) => ({ nodeId, reason: "NOT_REQUIRED" }));
|
|
103
|
+
|
|
104
|
+
for (const node of resolution.ordered) {
|
|
105
|
+
for (const capability of node.capabilities || []) {
|
|
106
|
+
if (!this.capabilities.has(capability)) {
|
|
107
|
+
return this._failure(
|
|
108
|
+
"MISSING_CAPABILITY",
|
|
109
|
+
{ nodeId: node.id, capability },
|
|
110
|
+
skipped,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!Object.hasOwn(this.handlers, node.id) || typeof this.handlers[node.id] !== "function") {
|
|
115
|
+
return this._failure("MISSING_HANDLER", { nodeId: node.id }, skipped);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let inputs;
|
|
120
|
+
let context;
|
|
121
|
+
try {
|
|
122
|
+
inputs = boundedClone(request.inputs || {}, this.limits);
|
|
123
|
+
context = boundedClone(request.context || {}, this.limits);
|
|
124
|
+
} catch {
|
|
125
|
+
return this._failure("BOUNDED_INPUT_EXCEEDED", {}, skipped);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const preflightInvoked = resolution.ordered.map((node) => ({
|
|
129
|
+
nodeId: node.id,
|
|
130
|
+
status: "INCOMPLETE",
|
|
131
|
+
evidence: [],
|
|
132
|
+
}));
|
|
133
|
+
try {
|
|
134
|
+
boundedClone({
|
|
135
|
+
status: "INCOMPLETE",
|
|
136
|
+
invoked: preflightInvoked,
|
|
137
|
+
skipped,
|
|
138
|
+
evidence: preflightInvoked,
|
|
139
|
+
error: { code: "BOUNDED_OUTPUT_EXCEEDED" },
|
|
140
|
+
}, this.limits);
|
|
141
|
+
} catch {
|
|
142
|
+
return this._failure("BOUNDED_OUTPUT_EXCEEDED");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.active = true;
|
|
146
|
+
const invoked = [];
|
|
147
|
+
const evidenceByNode = new Map();
|
|
148
|
+
try {
|
|
149
|
+
for (const node of resolution.ordered) {
|
|
150
|
+
const dependencyEvidence = (node.dependsOn || []).map((nodeId) => evidenceByNode.get(nodeId));
|
|
151
|
+
let handlerArguments;
|
|
152
|
+
try {
|
|
153
|
+
handlerArguments = {
|
|
154
|
+
nodeId: node.id,
|
|
155
|
+
inputs: boundedClone(selectKeys(inputs, node.inputKeys || []), this.limits),
|
|
156
|
+
context: boundedClone(selectKeys(context, node.contextKeys || []), this.limits),
|
|
157
|
+
dependencyEvidence: boundedClone(dependencyEvidence, this.limits),
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return this._boundedResult(
|
|
161
|
+
"INCOMPLETE",
|
|
162
|
+
invoked,
|
|
163
|
+
skipped,
|
|
164
|
+
{ code: "INVALID_ARGUMENTS", nodeId: node.id },
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let handlerResult;
|
|
169
|
+
try {
|
|
170
|
+
handlerResult = await this.handlers[node.id](handlerArguments);
|
|
171
|
+
} catch {
|
|
172
|
+
return this._boundedResult(
|
|
173
|
+
"INCOMPLETE",
|
|
174
|
+
invoked,
|
|
175
|
+
skipped,
|
|
176
|
+
{ code: "HANDLER_FAILED", nodeId: node.id },
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let result;
|
|
181
|
+
try {
|
|
182
|
+
result = boundedClone(handlerResult, this.limits);
|
|
183
|
+
} catch {
|
|
184
|
+
return this._boundedResult(
|
|
185
|
+
"INCOMPLETE",
|
|
186
|
+
invoked,
|
|
187
|
+
skipped,
|
|
188
|
+
{ code: "INVALID_EVIDENCE", nodeId: node.id },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (result && result.status === "INCOMPLETE"
|
|
193
|
+
&& result.error?.code === "RECURSIVE_INVOCATION") {
|
|
194
|
+
return this._boundedResult(
|
|
195
|
+
"INCOMPLETE",
|
|
196
|
+
invoked,
|
|
197
|
+
skipped,
|
|
198
|
+
{ code: "RECURSIVE_INVOCATION", nodeId: node.id },
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (!isPlainObject(result)
|
|
202
|
+
|| !NODE_STATUSES.has(result.status)
|
|
203
|
+
|| !Array.isArray(result.evidence)) {
|
|
204
|
+
return this._boundedResult(
|
|
205
|
+
"INCOMPLETE",
|
|
206
|
+
invoked,
|
|
207
|
+
skipped,
|
|
208
|
+
{ code: "INVALID_EVIDENCE", nodeId: node.id },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let evidence;
|
|
213
|
+
try {
|
|
214
|
+
evidence = boundedClone(result.evidence, this.limits);
|
|
215
|
+
} catch {
|
|
216
|
+
return this._boundedResult(
|
|
217
|
+
"INCOMPLETE",
|
|
218
|
+
invoked,
|
|
219
|
+
skipped,
|
|
220
|
+
{ code: "INVALID_EVIDENCE", nodeId: node.id },
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const entry = { nodeId: node.id, status: result.status, evidence };
|
|
225
|
+
invoked.push(entry);
|
|
226
|
+
evidenceByNode.set(node.id, entry);
|
|
227
|
+
const bounded = this._boundedResult(result.status, invoked, skipped);
|
|
228
|
+
if (bounded.error?.code === "BOUNDED_OUTPUT_EXCEEDED") return bounded;
|
|
229
|
+
if (result.status !== "PASS") {
|
|
230
|
+
return bounded;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} finally {
|
|
234
|
+
this.active = false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return this._boundedResult("PASS", invoked, skipped);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_boundedResult(status, invoked, skipped, error) {
|
|
241
|
+
const output = { status, invoked, skipped, evidence: invoked };
|
|
242
|
+
if (error) output.error = error;
|
|
243
|
+
try {
|
|
244
|
+
boundedClone(output, this.limits);
|
|
245
|
+
return output;
|
|
246
|
+
} catch {
|
|
247
|
+
if (invoked.length === 0 && error?.code) return safeFailure(error.code);
|
|
248
|
+
const compactInvoked = invoked.map(({ nodeId, status: invokedStatus }) => ({
|
|
249
|
+
nodeId,
|
|
250
|
+
status: invokedStatus,
|
|
251
|
+
evidence: [],
|
|
252
|
+
}));
|
|
253
|
+
const compactFailure = {
|
|
254
|
+
status: "INCOMPLETE",
|
|
255
|
+
invoked: compactInvoked,
|
|
256
|
+
skipped,
|
|
257
|
+
evidence: compactInvoked,
|
|
258
|
+
error: { code: "BOUNDED_OUTPUT_EXCEEDED" },
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
boundedClone(compactFailure, this.limits);
|
|
262
|
+
return compactFailure;
|
|
263
|
+
} catch {
|
|
264
|
+
// Preflight normally makes this unreachable; retain a bounded fail-closed fallback.
|
|
265
|
+
}
|
|
266
|
+
return safeFailure("BOUNDED_OUTPUT_EXCEEDED");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_failure(code, details = {}, skipped = []) {
|
|
271
|
+
return this._boundedResult("INCOMPLETE", [], skipped, { code, ...details });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_validateLimits(overrides) {
|
|
275
|
+
if (overrides !== undefined && !isPlainObject(overrides)) {
|
|
276
|
+
return { limit: "limits" };
|
|
277
|
+
}
|
|
278
|
+
for (const [name, ceiling] of Object.entries(LIMIT_CEILINGS)) {
|
|
279
|
+
const value = this.limits[name];
|
|
280
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > ceiling) {
|
|
281
|
+
return { limit: name };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
_indexMetadata() {
|
|
288
|
+
if (!isPlainObject(this.metadata)
|
|
289
|
+
|| typeof this.metadata.id !== "string"
|
|
290
|
+
|| this.metadata.id.length === 0
|
|
291
|
+
|| !Array.isArray(this.metadata.nodes)) {
|
|
292
|
+
return { code: "INVALID_METADATA", details: {} };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const node of this.metadata.nodes) {
|
|
296
|
+
if (!isPlainObject(node) || typeof node.id !== "string" || node.id.length === 0) {
|
|
297
|
+
return { code: "INVALID_METADATA", details: {} };
|
|
298
|
+
}
|
|
299
|
+
if (this.nodes.has(node.id)) {
|
|
300
|
+
return { code: "DUPLICATE_NODE_METADATA", details: { nodeId: node.id } };
|
|
301
|
+
}
|
|
302
|
+
for (const field of ["dependsOn", "capabilities", "inputKeys", "contextKeys"]) {
|
|
303
|
+
if (node[field] !== undefined
|
|
304
|
+
&& (!Array.isArray(node[field]) || node[field].some((item) => typeof item !== "string"))) {
|
|
305
|
+
return { code: "INVALID_METADATA", details: { nodeId: node.id, field } };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
this.nodes.set(node.id, node);
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_resolve(requestedNodes) {
|
|
314
|
+
const ordered = [];
|
|
315
|
+
const required = new Set();
|
|
316
|
+
const visiting = new Set();
|
|
317
|
+
|
|
318
|
+
const visit = (nodeId) => {
|
|
319
|
+
const node = this.nodes.get(nodeId);
|
|
320
|
+
if (!node) return { code: "UNKNOWN_NODE", details: { nodeId } };
|
|
321
|
+
if (visiting.has(nodeId)) return { code: "CYCLE", details: { nodeId } };
|
|
322
|
+
if (required.has(nodeId)) return null;
|
|
323
|
+
|
|
324
|
+
visiting.add(nodeId);
|
|
325
|
+
for (const dependencyId of node.dependsOn || []) {
|
|
326
|
+
const error = visit(dependencyId);
|
|
327
|
+
if (error) return error;
|
|
328
|
+
}
|
|
329
|
+
visiting.delete(nodeId);
|
|
330
|
+
required.add(nodeId);
|
|
331
|
+
ordered.push(node);
|
|
332
|
+
return null;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
for (const nodeId of requestedNodes) {
|
|
336
|
+
const error = visit(nodeId);
|
|
337
|
+
if (error) return { error };
|
|
338
|
+
}
|
|
339
|
+
return { ordered, required };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = { DEFAULT_LIMITS, SkillRuntime };
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { types } = require('node:util');
|
|
4
|
+
const { ContractValidationError, validateContractStructure } = require('@forge/contracts');
|
|
5
|
+
|
|
6
|
+
const TARGET_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
7
|
+
const SECRET_PATTERNS = [
|
|
8
|
+
/gh[pousr]_[a-z0-9]{20,}/i,
|
|
9
|
+
/github_pat_[a-z0-9_]{20,}/i,
|
|
10
|
+
/sk_(?:live|test)_[a-z0-9]{16,}/i,
|
|
11
|
+
/sk-[a-z0-9]{16,}/i,
|
|
12
|
+
/AKIA[0-9A-Z]{16}/i,
|
|
13
|
+
/(?:api[_-]?key|token|secret|password)\s*[:=]\s*\S{8,}/i,
|
|
14
|
+
];
|
|
15
|
+
const MAX_TARGETS = 32;
|
|
16
|
+
const MAX_TARGET_LENGTH = 128;
|
|
17
|
+
const PRIVATE_PATH_ROOTS = ['users', 'home', 'root'];
|
|
18
|
+
const MAX_PRIVATE_SCAN_LENGTH = 16_384;
|
|
19
|
+
const MONITOR_EVENT_ROW_FIELDS = Object.freeze([
|
|
20
|
+
'event_id', 'monitor_id', 'sequence', 'content_hash', 'envelope_json',
|
|
21
|
+
'artifact_digest', 'created_at',
|
|
22
|
+
]);
|
|
23
|
+
const MONITOR_CURSOR_ROW_FIELDS = Object.freeze([
|
|
24
|
+
'monitor_id', 'target', 'sequence', 'updated_at',
|
|
25
|
+
]);
|
|
26
|
+
const MONITOR_OUTBOX_ROW_FIELDS = Object.freeze([
|
|
27
|
+
'outbox_id', 'event_id', 'monitor_id', 'sequence', 'target', 'status',
|
|
28
|
+
'attempts', 'next_attempt_at', 'created_at',
|
|
29
|
+
]);
|
|
30
|
+
const MONITOR_TERMINAL_ROW_FIELDS = Object.freeze([
|
|
31
|
+
'monitor_id', 'content_hash', 'envelope_json', 'owner_run_id', 'terminal_state',
|
|
32
|
+
'last_sequence', 'evidence_digest', 'undelivered_cursor', 'created_at',
|
|
33
|
+
]);
|
|
34
|
+
const MONITOR_TAIL_FIELDS = Object.freeze([
|
|
35
|
+
'events', 'overflow', 'truncated_before_sequence',
|
|
36
|
+
]);
|
|
37
|
+
const MONITOR_DELIVERY_STATE_FIELDS = Object.freeze([
|
|
38
|
+
'cursors', 'outbox', 'terminal_receipt', 'overflow',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
class MonitorStoreError extends Error {
|
|
42
|
+
constructor(code, message, options = {}) {
|
|
43
|
+
super(message, Object.hasOwn(options, 'cause') ? { cause: options.cause } : undefined);
|
|
44
|
+
this.name = 'MonitorStoreError';
|
|
45
|
+
this.code = code;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class MonitorStaleError extends MonitorStoreError {
|
|
50
|
+
constructor(code, message, options) {
|
|
51
|
+
super(code, message, options);
|
|
52
|
+
this.name = 'MonitorStaleError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class MonitorConflictError extends MonitorStoreError {
|
|
57
|
+
constructor(code, message, options) {
|
|
58
|
+
super(code, message, options);
|
|
59
|
+
this.name = 'MonitorConflictError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class MonitorTerminalError extends MonitorStoreError {
|
|
64
|
+
constructor(code, message, options) {
|
|
65
|
+
super(code, message, options);
|
|
66
|
+
this.name = 'MonitorTerminalError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class MonitorUnavailableError extends MonitorStoreError {
|
|
71
|
+
constructor(code, message, options) {
|
|
72
|
+
super(code, message, options);
|
|
73
|
+
this.name = 'MonitorUnavailableError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const MONITOR_CONFLICT_CODES = new Set([
|
|
78
|
+
'MONITOR_EVENT_CONFLICT',
|
|
79
|
+
'MONITOR_TARGET_SET_CONFLICT',
|
|
80
|
+
'MONITOR_DELIVERY_CONFLICT',
|
|
81
|
+
'MONITOR_RECEIPT_CONFLICT',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
function publicMonitorError(error) {
|
|
85
|
+
if (error instanceof MonitorStoreError) return error;
|
|
86
|
+
const code = typeof error?.code === 'string' ? error.code : 'MONITOR_UNAVAILABLE';
|
|
87
|
+
const message = typeof error?.message === 'string' && error.message
|
|
88
|
+
? error.message
|
|
89
|
+
: 'Monitor durability provider unavailable';
|
|
90
|
+
const options = { cause: error };
|
|
91
|
+
if (code.startsWith('MONITOR_STALE_')) return new MonitorStaleError(code, message, options);
|
|
92
|
+
if (MONITOR_CONFLICT_CODES.has(code)) return new MonitorConflictError(code, message, options);
|
|
93
|
+
if (code === 'MONITOR_TERMINAL') return new MonitorTerminalError(code, message, options);
|
|
94
|
+
return new MonitorUnavailableError(code, message, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function callMonitorDriver(operation) {
|
|
98
|
+
try {
|
|
99
|
+
return await operation();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw publicMonitorError(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function ordinaryDataDescriptors(value, prototype) {
|
|
106
|
+
if (
|
|
107
|
+
!value
|
|
108
|
+
|| typeof value !== 'object'
|
|
109
|
+
|| types.isProxy(value)
|
|
110
|
+
|| Object.getPrototypeOf(value) !== prototype
|
|
111
|
+
|| !Object.isExtensible(value)
|
|
112
|
+
) return null;
|
|
113
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
114
|
+
if (Reflect.ownKeys(descriptors).some((key) => {
|
|
115
|
+
const descriptor = descriptors[key];
|
|
116
|
+
return typeof key !== 'string'
|
|
117
|
+
|| !descriptor.enumerable
|
|
118
|
+
|| !Object.hasOwn(descriptor, 'value');
|
|
119
|
+
})) return null;
|
|
120
|
+
return descriptors;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hasExactFields(descriptors, fields) {
|
|
124
|
+
const keys = Object.keys(descriptors);
|
|
125
|
+
return keys.length === fields.length && fields.every(field => Object.hasOwn(descriptors, field));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizeMonitorRow(value, fields) {
|
|
129
|
+
const descriptors = ordinaryDataDescriptors(value, null);
|
|
130
|
+
if (!descriptors || !hasExactFields(descriptors, fields) || Object.values(descriptors).some(({ value: field }) => (
|
|
131
|
+
field !== null
|
|
132
|
+
&& typeof field !== 'string'
|
|
133
|
+
&& !(typeof field === 'number' && Number.isFinite(field))
|
|
134
|
+
))) return value;
|
|
135
|
+
return Object.defineProperties({}, descriptors);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizeMonitorRows(value, fields) {
|
|
139
|
+
if (
|
|
140
|
+
!Array.isArray(value)
|
|
141
|
+
|| types.isProxy(value)
|
|
142
|
+
|| Object.getPrototypeOf(value) !== Array.prototype
|
|
143
|
+
|| !Object.isExtensible(value)
|
|
144
|
+
) return value;
|
|
145
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
146
|
+
const lengthDescriptor = descriptors.length;
|
|
147
|
+
if (
|
|
148
|
+
!lengthDescriptor
|
|
149
|
+
|| !Object.hasOwn(lengthDescriptor, 'value')
|
|
150
|
+
|| lengthDescriptor.value !== value.length
|
|
151
|
+
|| lengthDescriptor.enumerable
|
|
152
|
+
|| lengthDescriptor.configurable
|
|
153
|
+
|| !lengthDescriptor.writable
|
|
154
|
+
) return value;
|
|
155
|
+
const entryKeys = Reflect.ownKeys(descriptors).filter(key => key !== 'length');
|
|
156
|
+
if (entryKeys.length !== value.length || entryKeys.some((key) => {
|
|
157
|
+
const descriptor = descriptors[key];
|
|
158
|
+
const index = Number(key);
|
|
159
|
+
return typeof key !== 'string'
|
|
160
|
+
|| !Number.isInteger(index)
|
|
161
|
+
|| index < 0
|
|
162
|
+
|| index >= 4_294_967_295
|
|
163
|
+
|| index >= value.length
|
|
164
|
+
|| String(index) !== key
|
|
165
|
+
|| !descriptor.enumerable
|
|
166
|
+
|| !descriptor.writable
|
|
167
|
+
|| !descriptor.configurable
|
|
168
|
+
|| !Object.hasOwn(descriptor, 'value');
|
|
169
|
+
})) return value;
|
|
170
|
+
return value.map(row => normalizeMonitorRow(row, fields));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeMonitorResult(value, expectedFields, fields) {
|
|
174
|
+
const descriptors = ordinaryDataDescriptors(value, Object.prototype);
|
|
175
|
+
if (!descriptors || !hasExactFields(descriptors, expectedFields)) return value;
|
|
176
|
+
for (const [field, normalize] of Object.entries(fields)) {
|
|
177
|
+
if (descriptors[field]) descriptors[field].value = normalize(descriptors[field].value);
|
|
178
|
+
}
|
|
179
|
+
return Object.defineProperties({}, descriptors);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function readMonitorDriver(operation, normalize) {
|
|
183
|
+
return normalize(await callMonitorDriver(operation));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function hasNonWhitespacePathSegment(segment) {
|
|
187
|
+
return Boolean(segment && segment.trim());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function containsPrivateMonitorPath(value) {
|
|
191
|
+
if (typeof value !== 'string') return false;
|
|
192
|
+
if (value.length > MAX_PRIVATE_SCAN_LENGTH) return true;
|
|
193
|
+
const normalized = value.replaceAll('\\', '/').toLowerCase();
|
|
194
|
+
for (const root of PRIVATE_PATH_ROOTS) {
|
|
195
|
+
const marker = `/${root}/`;
|
|
196
|
+
let offset = 0;
|
|
197
|
+
while (true) {
|
|
198
|
+
const index = normalized.indexOf(marker, offset);
|
|
199
|
+
if (index < 0) break;
|
|
200
|
+
const segment = normalized.slice(index + marker.length).split('/')[0];
|
|
201
|
+
if (hasNonWhitespacePathSegment(segment)) return true;
|
|
202
|
+
offset = index + marker.length;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (let code = 97; code <= 122; code += 1) {
|
|
206
|
+
const marker = `${String.fromCharCode(code)}:/users/`;
|
|
207
|
+
const index = normalized.indexOf(marker);
|
|
208
|
+
if (index >= 0 && hasNonWhitespacePathSegment(
|
|
209
|
+
normalized.slice(index + marker.length).split('/')[0],
|
|
210
|
+
)) return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function containsPrivateMonitorData(value) {
|
|
216
|
+
if (typeof value === 'string') {
|
|
217
|
+
return SECRET_PATTERNS.some(pattern => pattern.test(value)) || containsPrivateMonitorPath(value);
|
|
218
|
+
}
|
|
219
|
+
if (!value || typeof value !== 'object') return false;
|
|
220
|
+
return Object.entries(value).some(([key, nestedValue]) => (
|
|
221
|
+
SECRET_PATTERNS.some(pattern => pattern.test(key))
|
|
222
|
+
|| containsPrivateMonitorPath(key)
|
|
223
|
+
|| containsPrivateMonitorData(nestedValue)
|
|
224
|
+
));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assertEnvelope(envelope, schemaId) {
|
|
228
|
+
const result = validateContractStructure(envelope);
|
|
229
|
+
if (!result.ok || envelope?.schema_id !== schemaId) {
|
|
230
|
+
throw new ContractValidationError(result.ok
|
|
231
|
+
? [{ path: '$.schema_id', code: 'UNEXPECTED_SCHEMA' }]
|
|
232
|
+
: result.errors);
|
|
233
|
+
}
|
|
234
|
+
const privacyEnvelope = schemaId === 'forge.memory.delivery-receipt.v1' && envelope?.payload
|
|
235
|
+
? { ...envelope, payload: { ...envelope.payload, target: '' } }
|
|
236
|
+
: envelope;
|
|
237
|
+
if (containsPrivateMonitorData(privacyEnvelope)) {
|
|
238
|
+
throw new Error(`private content rejected from ${schemaId}`);
|
|
239
|
+
}
|
|
240
|
+
return envelope;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function assertTarget(target) {
|
|
244
|
+
if (typeof target !== 'string' || target.length === 0 || target.length > MAX_TARGET_LENGTH
|
|
245
|
+
|| !TARGET_PATTERN.test(target) || SECRET_PATTERNS.some(pattern => pattern.test(target)) || containsPrivateMonitorPath(target)) {
|
|
246
|
+
throw new Error('invalid or private monitor delivery target');
|
|
247
|
+
}
|
|
248
|
+
return target;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function normalizeTargets(targets) {
|
|
252
|
+
if (!Array.isArray(targets) || targets.length === 0 || targets.length > MAX_TARGETS) {
|
|
253
|
+
throw new Error(`monitor delivery targets must contain 1-${MAX_TARGETS} entries`);
|
|
254
|
+
}
|
|
255
|
+
return [...new Set(targets.map(assertTarget))];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function assertMonitorId(monitorId) {
|
|
259
|
+
if (typeof monitorId !== 'string' || monitorId.length === 0 || monitorId.length > 128) {
|
|
260
|
+
throw new TypeError('monitorId must be a bounded non-empty string');
|
|
261
|
+
}
|
|
262
|
+
return monitorId;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function createMonitorStore(driver) {
|
|
266
|
+
if (!driver || typeof driver.appendMonitorEvent !== 'function') {
|
|
267
|
+
throw new Error('Monitor store requires a monitor-capable SQLite driver');
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
async appendEvent(envelope, targets, config = {}) {
|
|
271
|
+
assertEnvelope(envelope, 'forge.memory.monitor-event.v1');
|
|
272
|
+
return callMonitorDriver(() => driver.appendMonitorEvent(envelope, normalizeTargets(targets), config));
|
|
273
|
+
},
|
|
274
|
+
async recordDeliveryReceipt(envelope, config = {}) {
|
|
275
|
+
assertEnvelope(envelope, 'forge.memory.delivery-receipt.v1');
|
|
276
|
+
assertTarget(envelope.payload.target);
|
|
277
|
+
return callMonitorDriver(() => driver.recordMonitorDeliveryReceipt(envelope, config));
|
|
278
|
+
},
|
|
279
|
+
async recordTerminalReceipt(envelope, config = {}) {
|
|
280
|
+
assertEnvelope(envelope, 'forge.memory.monitor-receipt.v1');
|
|
281
|
+
return callMonitorDriver(() => driver.recordMonitorTerminalReceipt(envelope, config));
|
|
282
|
+
},
|
|
283
|
+
getEvent(eventId, config = {}) {
|
|
284
|
+
if (typeof eventId !== 'string' || !eventId || eventId.length > 255) {
|
|
285
|
+
throw new TypeError('eventId must be a bounded non-empty string');
|
|
286
|
+
}
|
|
287
|
+
return readMonitorDriver(
|
|
288
|
+
() => driver.getMonitorEvent(eventId, config),
|
|
289
|
+
value => normalizeMonitorRow(value, MONITOR_EVENT_ROW_FIELDS),
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
readEventTail(monitorId, options = {}, config = {}) {
|
|
293
|
+
return readMonitorDriver(
|
|
294
|
+
() => driver.readMonitorEventTail(assertMonitorId(monitorId), options, config),
|
|
295
|
+
value => normalizeMonitorResult(value, MONITOR_TAIL_FIELDS, {
|
|
296
|
+
events: rows => normalizeMonitorRows(rows, MONITOR_EVENT_ROW_FIELDS),
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
readDeliveryState(monitorId, options = {}, config = {}) {
|
|
301
|
+
return readMonitorDriver(
|
|
302
|
+
() => driver.readMonitorDeliveryState(assertMonitorId(monitorId), options, config),
|
|
303
|
+
value => normalizeMonitorResult(value, MONITOR_DELIVERY_STATE_FIELDS, {
|
|
304
|
+
cursors: rows => normalizeMonitorRows(rows, MONITOR_CURSOR_ROW_FIELDS),
|
|
305
|
+
outbox: rows => normalizeMonitorRows(rows, MONITOR_OUTBOX_ROW_FIELDS),
|
|
306
|
+
terminal_receipt: row => normalizeMonitorRow(row, MONITOR_TERMINAL_ROW_FIELDS),
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
},
|
|
310
|
+
listEvents(monitorId, config = {}) {
|
|
311
|
+
return readMonitorDriver(
|
|
312
|
+
() => driver.listMonitorEvents(assertMonitorId(monitorId), config),
|
|
313
|
+
rows => normalizeMonitorRows(rows, MONITOR_EVENT_ROW_FIELDS),
|
|
314
|
+
);
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = {
|
|
320
|
+
...require('./src/authority-provider'),
|
|
321
|
+
...require('./src/backend-registry'),
|
|
322
|
+
...require('./src/feedback-intake'),
|
|
323
|
+
...require('./src/pr-lifecycle-authority'),
|
|
324
|
+
...require('./src/usage-evidence'),
|
|
325
|
+
MonitorConflictError,
|
|
326
|
+
MonitorStaleError,
|
|
327
|
+
MonitorStoreError,
|
|
328
|
+
MonitorTerminalError,
|
|
329
|
+
MonitorUnavailableError,
|
|
330
|
+
createMonitorStore,
|
|
331
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forge/memory",
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
|
+
"description": "Forge Memory public backend boundary",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"src",
|
|
10
|
+
"!src/**/*.test.js"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "bun test"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@forge/contracts": "0.1.0-beta.6"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.16.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/harshanandak/forge.git",
|
|
28
|
+
"directory": "packages/memory"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/harshanandak/forge#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/harshanandak/forge/issues"
|
|
33
|
+
}
|
|
34
|
+
}
|