@plurnk/plurnk-service 3.1.0 → 3.2.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/.env.defaults +22 -17
- package/INSTALL.md +20 -1
- package/SPEC.md +333 -88
- package/dist/build-info.json +1 -1
- package/dist/completions/_plurnk-service +1 -1
- package/dist/completions/plurnk-service.bash +1 -1
- package/dist/completions/plurnk-service.fish +1 -0
- package/dist/content/path-mimetype.d.ts.map +1 -1
- package/dist/content/path-mimetype.js +2 -8
- package/dist/content/path-mimetype.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +22 -10
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/{schemes/_derivation-use.d.ts → core/ContentUse.d.ts} +3 -3
- package/dist/core/ContentUse.d.ts.map +1 -0
- package/dist/{schemes/_derivation-use.js → core/ContentUse.js} +8 -7
- package/dist/core/ContentUse.js.map +1 -0
- package/dist/core/DataStatementRunner.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.js +8 -20
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +9 -1
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +144 -71
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/EditMutations.js +1 -1
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/Engine.d.ts +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +3 -3
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/FileAddress.d.ts +12 -0
- package/dist/core/FileAddress.d.ts.map +1 -0
- package/dist/core/FileAddress.js +73 -0
- package/dist/core/FileAddress.js.map +1 -0
- package/dist/core/FilePublication.d.ts +15 -0
- package/dist/core/FilePublication.d.ts.map +1 -0
- package/dist/core/FilePublication.js +143 -0
- package/dist/core/FilePublication.js.map +1 -0
- package/dist/core/FileSnapshot.d.ts +26 -0
- package/dist/core/FileSnapshot.d.ts.map +1 -0
- package/dist/core/FileSnapshot.js +179 -0
- package/dist/core/FileSnapshot.js.map +1 -0
- package/dist/core/KillHandler.d.ts.map +1 -1
- package/dist/core/KillHandler.js +2 -1
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/MembershipMaterialization.d.ts.map +1 -1
- package/dist/core/MembershipMaterialization.js +87 -143
- package/dist/core/MembershipMaterialization.js.map +1 -1
- package/dist/core/MutationEffects.d.ts +2 -1
- package/dist/core/MutationEffects.d.ts.map +1 -1
- package/dist/core/MutationEffects.js +14 -1
- package/dist/core/MutationEffects.js.map +1 -1
- package/dist/core/PacketBuilder.sql +8 -0
- package/dist/core/PathGlob.d.ts +6 -0
- package/dist/core/PathGlob.d.ts.map +1 -0
- package/dist/core/PathGlob.js +24 -0
- package/dist/core/PathGlob.js.map +1 -0
- package/dist/core/ProposalLifecycle.d.ts +2 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +45 -33
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProposalLifecycle.sql +4 -0
- package/dist/core/ResourceBindings.d.ts.map +1 -1
- package/dist/core/ResourceBindings.js +4 -3
- package/dist/core/ResourceBindings.js.map +1 -1
- package/dist/core/ResourceSelector.d.ts +1 -2
- package/dist/core/ResourceSelector.d.ts.map +1 -1
- package/dist/core/ResourceSelector.js +30 -9
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/ResourceTransfers.d.ts +1 -1
- package/dist/core/ResourceTransfers.d.ts.map +1 -1
- package/dist/core/ResourceTransfers.js +26 -11
- package/dist/core/ResourceTransfers.js.map +1 -1
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +6 -4
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +1 -0
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +5 -0
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/git-membership.d.ts +1 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +9 -11
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/glob_match.js +3 -3
- package/dist/core/glob_match.js.map +1 -1
- package/dist/core/mutation-types.d.ts +3 -0
- package/dist/core/mutation-types.d.ts.map +1 -1
- package/dist/core/namespace.d.ts +4 -1
- package/dist/core/namespace.d.ts.map +1 -1
- package/dist/core/namespace.js +14 -3
- package/dist/core/namespace.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +11 -9
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +3 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +23 -11
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/scheme-types.d.ts +1 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/sqlite-synchronous.d.ts +2 -0
- package/dist/core/sqlite-synchronous.d.ts.map +1 -0
- package/dist/core/sqlite-synchronous.js +4 -0
- package/dist/core/sqlite-synchronous.js.map +1 -0
- package/dist/evidence/EvidenceReader.d.ts.map +1 -1
- package/dist/evidence/EvidenceReader.js +8 -1
- package/dist/evidence/EvidenceReader.js.map +1 -1
- package/dist/evidence/digest.sql +6 -1
- package/dist/man/plurnk-service.1 +32 -21
- package/dist/schemes/Env.d.ts +1 -1
- package/dist/schemes/Env.d.ts.map +1 -1
- package/dist/schemes/Env.js +2 -2
- package/dist/schemes/Env.js.map +1 -1
- package/dist/schemes/File.d.ts +1 -0
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +233 -268
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +64 -7
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Worker.js +1 -1
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +2 -0
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +8 -5
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +15 -15
- package/dist/schemes/_entry-find.d.ts +1 -0
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +15 -10
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts +2 -2
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +7 -5
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +9 -7
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_path-scope.d.ts.map +1 -1
- package/dist/schemes/_path-scope.js +12 -4
- package/dist/schemes/_path-scope.js.map +1 -1
- package/dist/schemes/_search-index.js +3 -3
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/exec-env.d.ts.map +1 -1
- package/dist/schemes/exec-env.js +7 -22
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +40 -68
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +1 -3
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +30 -5
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +19 -11
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js +15 -8
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/FunctionalityManager.d.ts +1 -1
- package/dist/server/FunctionalityManager.d.ts.map +1 -1
- package/dist/server/FunctionalityManager.js +5 -1
- package/dist/server/FunctionalityManager.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +12 -12
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/Retention.d.ts.map +1 -1
- package/dist/server/Retention.js +5 -5
- package/dist/server/Retention.js.map +1 -1
- package/dist/server/Retention.sql +49 -23
- package/dist/server/lifecycle-recovery.sql +4 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -0
- package/dist/service.js.map +1 -1
- package/docs/copy-move.md +3 -0
- package/docs/env.md +12 -6
- package/docs/members.md +16 -1
- package/migrations/023_file_revisions.sql +54 -0
- package/migrations/024_proposal_decisions.sql +34 -0
- package/migrations/025_response_retention.sql +30 -0
- package/package.json +33 -31
- package/dist/schemes/_derivation-use.d.ts.map +0 -1
- package/dist/schemes/_derivation-use.js.map +0 -1
package/dist/core/Dispatcher.js
CHANGED
|
@@ -3,10 +3,11 @@ import { parsePath } from "@plurnk/plurnk-parser";
|
|
|
3
3
|
import { ConfigurationError, Knob } from "@plurnk/plurnk-meta";
|
|
4
4
|
import { TurnDisposition, UNKNOWN_POSITION } from "@plurnk/plurnk-contracts";
|
|
5
5
|
import Turn from "./Turn.js";
|
|
6
|
-
import { foldAuthorityIntoPath, renderAddress, renderTarget, schemeNameOf } from "./plurnk-uri.js";
|
|
6
|
+
import { entryAddress, foldAuthorityIntoPath, renderAddress, renderAuthoredTarget, renderTarget, schemeNameOf } from "./plurnk-uri.js";
|
|
7
7
|
import WorkerName from "./WorkerName.js";
|
|
8
8
|
import { PathSyntax } from "@plurnk/plurnk-contracts";
|
|
9
9
|
import Namespace from "./namespace.js";
|
|
10
|
+
import FileAddress from "./FileAddress.js";
|
|
10
11
|
import CapabilityResolver from "./CapabilityResolver.js";
|
|
11
12
|
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
12
13
|
import Results from "./results.js";
|
|
@@ -22,7 +23,7 @@ import BodyPreview from "../content/body-preview.js";
|
|
|
22
23
|
import ReadResolve from "../content/read-resolve.js";
|
|
23
24
|
import LogVisibility from "./LogVisibility.js";
|
|
24
25
|
import PacketWire from "./packet-wire.js";
|
|
25
|
-
import { resultPrefix, resultSize, unfitResult } from "./ContextFit.js";
|
|
26
|
+
import { isContextReceipt, reserved, resultPrefix, resultSize, unfitResult } from "./ContextFit.js";
|
|
26
27
|
import { TextCoordinates } from "@plurnk/plurnk-mimetypes";
|
|
27
28
|
import EntryAddressBinding from "./EntryAddressBinding.js";
|
|
28
29
|
import WorkerControlHandler from "./WorkerControlHandler.js";
|
|
@@ -91,7 +92,6 @@ class Dispatcher {
|
|
|
91
92
|
this.#resourceSelector = new ResourceSelector({
|
|
92
93
|
schemes,
|
|
93
94
|
admitRead: (statement, ctx) => this.#checkCapabilities(statement, ctx),
|
|
94
|
-
canonicalFilePath: (pathname, workspaceId) => this.#canonicalFilePath(pathname, workspaceId),
|
|
95
95
|
prepareDataRepresentation: (args) => this.#prepareDataRepresentation({
|
|
96
96
|
...args,
|
|
97
97
|
handler: args.handler,
|
|
@@ -247,13 +247,10 @@ class Dispatcher {
|
|
|
247
247
|
if (key !== null)
|
|
248
248
|
target.pathname = key;
|
|
249
249
|
}
|
|
250
|
-
async #canonicalFilePath(pathname, workspaceId) {
|
|
251
|
-
return Namespace.canonicalizeSpelling(pathname, await this.#workspaceRoot(workspaceId));
|
|
252
|
-
}
|
|
253
250
|
async #editTargetIdentity(statement, workspaceId, _workerId) {
|
|
254
251
|
const target = this.#extractTarget(statement.target, workspaceId);
|
|
255
252
|
await this.#canonColumns(target, workspaceId);
|
|
256
|
-
return renderTarget(target);
|
|
253
|
+
return renderTarget(target, await this.#workspaceRoot(workspaceId));
|
|
257
254
|
}
|
|
258
255
|
// {§kill-scope-entry} — a scoped KILL on an entry-bearing scheme is one EDIT with an empty
|
|
259
256
|
// body over the same marker: prepared, gated, and merged as an EDIT while its log row
|
|
@@ -295,9 +292,9 @@ class Dispatcher {
|
|
|
295
292
|
async dispatch(context) {
|
|
296
293
|
await this.#operationEvent(context, "started");
|
|
297
294
|
await this.#noticeFilePaths(context);
|
|
298
|
-
const
|
|
299
|
-
await this.#operationEvent(context, "settled", result);
|
|
300
|
-
return
|
|
295
|
+
const execution = await this.#dispatch(context);
|
|
296
|
+
await this.#operationEvent(context, "settled", execution.result);
|
|
297
|
+
return execution;
|
|
301
298
|
}
|
|
302
299
|
// {§file-path-normalization} Authored operands, once, before internal fan-out.
|
|
303
300
|
async #noticeFilePaths({ statement, origin, workspaceId, workerId, loopId }) {
|
|
@@ -306,22 +303,29 @@ class Dispatcher {
|
|
|
306
303
|
const targets = statement.op === "COPY" || statement.op === "MOVE"
|
|
307
304
|
? [statement.source.target, statement.destination.target]
|
|
308
305
|
: [statement.target];
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
306
|
+
const normalized = targets.flatMap((target) => {
|
|
307
|
+
if (target === null || schemeNameOf(target) !== "file")
|
|
308
|
+
return [];
|
|
309
|
+
const pathname = FileAddress.pathname(target);
|
|
310
|
+
return typeof pathname === "string" && (pathname.startsWith("/") || Namespace.isHomeRelative(pathname))
|
|
311
|
+
? [{ target, pathname }] : [];
|
|
312
|
+
});
|
|
313
|
+
if (normalized.length === 0)
|
|
313
314
|
return;
|
|
314
315
|
const root = await this.#workspaceRoot(workspaceId);
|
|
315
316
|
if (root === null || root === "/")
|
|
316
317
|
return;
|
|
317
|
-
for (const pathname of
|
|
318
|
+
for (const { target, pathname } of normalized) {
|
|
318
319
|
const key = Namespace.canonicalizeSpelling(pathname, root);
|
|
319
320
|
if (key === null)
|
|
320
321
|
continue;
|
|
322
|
+
const address = renderTarget({ scheme: null, pathname: key }, root, target);
|
|
323
|
+
if (address?.startsWith("file://"))
|
|
324
|
+
continue;
|
|
321
325
|
this.#notices.push(workspaceId, workerId, loopId, {
|
|
322
326
|
source: "scheme:file", kind: "path_normalized", level: "warn",
|
|
323
327
|
directive: "YOU SHOULD address the resolved path.",
|
|
324
|
-
message: `Path resolved to '${
|
|
328
|
+
message: `Path resolved to '${address || "."}'.`,
|
|
325
329
|
});
|
|
326
330
|
}
|
|
327
331
|
}
|
|
@@ -336,11 +340,9 @@ class Dispatcher {
|
|
|
336
340
|
...(result === undefined ? {} : { result }),
|
|
337
341
|
});
|
|
338
342
|
}
|
|
339
|
-
async #dispatch(context) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
let result = await ResourceBindings.using(this.#schemes, this.#buildSchemeCtx(context), (ctx) => this.#dispatchOne(context, ctx));
|
|
343
|
+
async #dispatch(context, targetsResolved = false) {
|
|
344
|
+
const execution = await ResourceBindings.using(this.#schemes, this.#buildSchemeCtx(context), (ctx) => this.#dispatchOne(context, ctx, targetsResolved));
|
|
345
|
+
let { result } = execution;
|
|
344
346
|
const edit = context.statement.op === "EDIT" ? context.statement : this.#scopedEntryEdits.get(context.statement);
|
|
345
347
|
if (edit !== undefined) {
|
|
346
348
|
// {§edit-batch-merges} — a proposal's apply result replaces the projected one; the
|
|
@@ -361,7 +363,7 @@ class Dispatcher {
|
|
|
361
363
|
await ResourceBindings.using(this.#schemes, this.#buildSchemeCtx(noteContext), (ctx) => this.#dispatchOne(noteContext, ctx));
|
|
362
364
|
result = Results.assert({ ...result, rowsWritten: (result.rowsWritten ?? 1) + 1 });
|
|
363
365
|
}
|
|
364
|
-
return result;
|
|
366
|
+
return { ...execution, result };
|
|
365
367
|
}
|
|
366
368
|
// {§matcher-refusal} — a matcher the parser could not read refuses its one operation with the parser's
|
|
367
369
|
// diagnostic and working form: the operation's own row, soft on the rail, never a contract strike.
|
|
@@ -380,57 +382,109 @@ class Dispatcher {
|
|
|
380
382
|
return statement.body.trim() === "" ? null : statement.body;
|
|
381
383
|
}
|
|
382
384
|
static #globTarget(target) {
|
|
383
|
-
|
|
385
|
+
if (target === null)
|
|
386
|
+
return false;
|
|
387
|
+
if (target.kind === "local")
|
|
388
|
+
return PathSyntax.hasGlob(target.raw);
|
|
389
|
+
// Authority-only worker addresses name actors, not their resource trees.
|
|
390
|
+
if (target.scheme === "worker" && (target.pathname === "" || target.pathname === "/"))
|
|
391
|
+
return false;
|
|
392
|
+
return PathSyntax.hasGlob(target.pathname) || PathSyntax.hasGlob(target.hostname ?? "");
|
|
384
393
|
}
|
|
385
|
-
// {§
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
async #fanOutRead(context, statement) {
|
|
391
|
-
const survey = {
|
|
392
|
-
op: "FIND", aside: statement.aside, target: statement.target, metadata: statement.metadata,
|
|
393
|
-
matcher: statement.matcher, lineMarker: null, body: null, position: statement.position,
|
|
394
|
-
};
|
|
395
|
-
const found = await ResourceBindings.using(this.#schemes, this.#buildSchemeCtx(context), (ctx) => this.#dataRun.run(schemeNameOf(statement.target), survey, ctx));
|
|
394
|
+
// {§bulk-resource-targeting} Selection and exact execution share the ordinary owners.
|
|
395
|
+
async #findTargets(survey, ctx) {
|
|
396
|
+
const found = await this.#dataRun.run(schemeNameOf(survey.target), survey, ctx);
|
|
397
|
+
if (found.status >= 400)
|
|
398
|
+
return { result: found };
|
|
396
399
|
const paths = found.status < 300
|
|
397
400
|
? (found.results ?? []).flatMap((item) => Array.isArray(item) && !("items" in item[0]) ? [item[0].path] : [])
|
|
398
401
|
: [];
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
: { status: 204, matched: 0, matcher: statement.matcher.raw, detail: `No line under ${statement.target.raw} matched the pattern.` });
|
|
403
|
-
const logEntryId = await this.#logWriter.writeLog({
|
|
404
|
-
statement, result, workspaceId: context.workspaceId, workerId: context.workerId, loopId: context.loopId,
|
|
405
|
-
turnId: context.turnId, sequence: context.sequence, origin: context.origin, curationPlan: null, modelCallId: null,
|
|
406
|
-
});
|
|
407
|
-
context.onDispatch?.(logEntryId);
|
|
408
|
-
await this.#notifySettled(context, logEntryId);
|
|
409
|
-
return result;
|
|
402
|
+
const matched = typeof found.matchingPathCount === "number" ? found.matchingPathCount : paths.length;
|
|
403
|
+
if (survey.lineMarker !== null && matched !== paths.length) {
|
|
404
|
+
throw new InvalidOperationResultError("Unbounded FIND did not return its complete resource selection.");
|
|
410
405
|
}
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const target = parsePath(path);
|
|
406
|
+
const targets = paths.map((path) => {
|
|
407
|
+
const selected = survey.target?.fragment == null ? path : `${path.split("#")[0]}#${survey.target.fragment}`;
|
|
408
|
+
const target = parsePath(selected);
|
|
415
409
|
if (target === null)
|
|
416
|
-
throw new InvalidOperationResultError(`FIND named an unparseable path: ${
|
|
417
|
-
|
|
418
|
-
|
|
410
|
+
throw new InvalidOperationResultError(`FIND named an unparseable path: ${selected}`);
|
|
411
|
+
return target;
|
|
412
|
+
});
|
|
413
|
+
return { targets, matched };
|
|
414
|
+
}
|
|
415
|
+
async #selectTargets(statement, ctx) {
|
|
416
|
+
if (statement.op === "COPY" || statement.op === "MOVE") {
|
|
417
|
+
if (![statement.source, statement.destination].some(({ target }) => _a.#globTarget(target)))
|
|
418
|
+
return null;
|
|
419
|
+
let resolved = statement;
|
|
420
|
+
for (const operand of ["source", "destination"]) {
|
|
421
|
+
const selection = statement[operand];
|
|
422
|
+
if (!_a.#globTarget(selection.target))
|
|
423
|
+
continue;
|
|
424
|
+
const found = await this.#findTargets({
|
|
425
|
+
...selection, op: "FIND", matcher: null, lineMarker: { marks: [1, -1] },
|
|
426
|
+
aside: statement.aside, position: statement.position, body: null,
|
|
427
|
+
}, ctx);
|
|
428
|
+
if ("result" in found)
|
|
429
|
+
return found;
|
|
430
|
+
if (found.targets.length !== 1)
|
|
431
|
+
return { result: _a.#failure("target-cardinality", 400, `${statement.op} ${operand} matched ${found.matched} resources; expected one.`, {}, { operand, matchingPathCount: found.matched, recovery: "Name one source and one destination per pair.", retryable: false }) };
|
|
432
|
+
resolved = { ...resolved, [operand]: { ...selection, target: found.targets[0] } };
|
|
433
|
+
}
|
|
434
|
+
return { statements: [resolved] };
|
|
419
435
|
}
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
436
|
+
if (!(statement.op === "READ" || statement.op === "EDIT" || statement.op === "KILL"))
|
|
437
|
+
return null;
|
|
438
|
+
if (!_a.#globTarget(statement.target) || (statement.op === "KILL" && schemeNameOf(statement.target) === "log"))
|
|
439
|
+
return null;
|
|
440
|
+
const found = await this.#findTargets({
|
|
441
|
+
op: "FIND", aside: statement.aside, target: statement.target, metadata: statement.metadata,
|
|
442
|
+
matcher: statement.op === "READ" ? statement.matcher : null,
|
|
443
|
+
lineMarker: statement.op === "READ" ? null : { marks: [1, -1] }, body: null, position: statement.position,
|
|
444
|
+
}, ctx);
|
|
445
|
+
if ("result" in found)
|
|
446
|
+
return found;
|
|
447
|
+
if (found.targets.length === 0)
|
|
448
|
+
return { result: Results.assert(statement.op === "READ" && statement.matcher !== null
|
|
449
|
+
? { status: 204, matched: 0, matcher: statement.matcher.raw, detail: `No line under ${statement.target.raw} matched the pattern.` }
|
|
450
|
+
: { status: 204, detail: `No path matched ${statement.target.raw}.` }) };
|
|
451
|
+
if (statement.op === "READ" && found.matched > found.targets.length) {
|
|
452
|
+
this.#notices.push(ctx.workspaceId, ctx.workerId, ctx.loopId, {
|
|
453
|
+
source: "engine:dispatcher", kind: "read_fanout_bounded", level: "warn",
|
|
425
454
|
directive: "YOU SHOULD narrow the glob, or FIND first.",
|
|
426
|
-
message: `READ ${statement.target.raw} matched ${
|
|
455
|
+
message: `READ ${statement.target.raw} matched ${found.matched} paths; the first ${found.targets.length} were selected.`,
|
|
427
456
|
});
|
|
428
457
|
}
|
|
429
|
-
return
|
|
458
|
+
return { statements: found.targets.map((target) => ({ ...statement, target })), fanout: { target: statement.target.raw, matched: found.matched } };
|
|
430
459
|
}
|
|
431
|
-
async #
|
|
460
|
+
async #dispatchSelection(context, selection) {
|
|
461
|
+
const admitted = selection.statements.slice(0, context.remainingCommands);
|
|
462
|
+
const results = [];
|
|
463
|
+
let rowsWritten = 0;
|
|
464
|
+
for (const [index, statement] of admitted.entries()) {
|
|
465
|
+
const { result } = await this.#dispatch({
|
|
466
|
+
...context, statement, sequence: context.sequence + rowsWritten,
|
|
467
|
+
...(selection.fanout === undefined ? {} : { fanout: { ...selection.fanout, index, count: admitted.length } }),
|
|
468
|
+
...(context.fit === undefined ? {} : { fit: reserved(context.fit, admitted.length - index - 1) }),
|
|
469
|
+
}, true);
|
|
470
|
+
results.push(result);
|
|
471
|
+
rowsWritten += result.rowsWritten ?? 1;
|
|
472
|
+
if (context.failOnOperationError && result.status >= 400 && !isContextReceipt(result))
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
if (results.length === 0)
|
|
476
|
+
throw new Error("Dispatch requires an admitted operation.");
|
|
477
|
+
return { result: { ...(results.find(({ status }) => status >= 400) ?? results.at(-1)), rowsWritten },
|
|
478
|
+
commandsExecuted: results.length, commandsOmitted: selection.statements.length - admitted.length };
|
|
479
|
+
}
|
|
480
|
+
// {§bulk-resource-targeting} Dispatch accounting is core-owned, never a scheme's extension data.
|
|
481
|
+
static #singleExecution(result) {
|
|
482
|
+
return { result, commandsExecuted: 1, commandsOmitted: 0 };
|
|
483
|
+
}
|
|
484
|
+
async #dispatchOne(context, schemeCtx, targetsResolved = false) {
|
|
432
485
|
const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, } = context;
|
|
433
486
|
let result;
|
|
487
|
+
let resolved = null;
|
|
434
488
|
let curationPlan = null;
|
|
435
489
|
const denial = this.#checkWritable(statement, origin, workspaceId)
|
|
436
490
|
?? await this.#checkCapabilities(statement, schemeCtx)
|
|
@@ -445,7 +499,14 @@ class Dispatcher {
|
|
|
445
499
|
// skips it. Logging failures (#writeLog throws) are NOT caught —
|
|
446
500
|
// those are system failures.
|
|
447
501
|
try {
|
|
448
|
-
|
|
502
|
+
const selection = targetsResolved ? null : await this.#selectTargets(statement, schemeCtx);
|
|
503
|
+
if (selection !== null && !("result" in selection)) {
|
|
504
|
+
resolved = selection;
|
|
505
|
+
}
|
|
506
|
+
else if (selection !== null) {
|
|
507
|
+
result = selection.result;
|
|
508
|
+
}
|
|
509
|
+
else if (statement.op === "EDIT") {
|
|
449
510
|
result = await this.#resourceMutations.edit(statement, schemeCtx, context.editSequence);
|
|
450
511
|
}
|
|
451
512
|
else if (statement.op === "SEND" && statement.target === null) {
|
|
@@ -516,6 +577,12 @@ class Dispatcher {
|
|
|
516
577
|
}
|
|
517
578
|
}
|
|
518
579
|
}
|
|
580
|
+
// Child dispatch includes persistence and proposal settlement; their failures are
|
|
581
|
+
// internal, not exceptions from a scheme handler.
|
|
582
|
+
if (resolved !== null)
|
|
583
|
+
return this.#dispatchSelection(context, resolved);
|
|
584
|
+
if (result === undefined)
|
|
585
|
+
throw new Error("Operation dispatch produced neither targets nor a result.");
|
|
519
586
|
result = await this.#fitResult(context, statement, result);
|
|
520
587
|
// {§configuration-repair-path}: decide admission before persisting a proposed row.
|
|
521
588
|
let autoAccept = false;
|
|
@@ -538,7 +605,7 @@ class Dispatcher {
|
|
|
538
605
|
}
|
|
539
606
|
}
|
|
540
607
|
}
|
|
541
|
-
// {§
|
|
608
|
+
// {§bulk-resource-targeting} — a row on behalf of a glob names the statement it came from.
|
|
542
609
|
if (context.fanout !== undefined) {
|
|
543
610
|
const attrs = typeof result.attrs === "object" && result.attrs !== null ? result.attrs : {};
|
|
544
611
|
result = { ...result, attrs: { ...attrs, fanout: context.fanout } };
|
|
@@ -569,6 +636,7 @@ class Dispatcher {
|
|
|
569
636
|
// result the caller sees, so runTurn never branches on a pending state.
|
|
570
637
|
if (_a.#isProposal(statement, result)) {
|
|
571
638
|
if (autoAccept) {
|
|
639
|
+
await this.#proposals.recordDecision(logEntryId, { decision: "accept" });
|
|
572
640
|
const initialSettlement = await this.#proposals.workerApply(statement, result, { decision: "accept" }, { workspaceId, workerId, loopId, turnId, resources: schemeCtx.resources });
|
|
573
641
|
const effective = await this.#resourceMutations.settleProposal({
|
|
574
642
|
statement,
|
|
@@ -579,7 +647,7 @@ class Dispatcher {
|
|
|
579
647
|
});
|
|
580
648
|
const post = await this.#proposals.applyResolution(logEntryId, effective);
|
|
581
649
|
await this.#notifySettled(context, logEntryId);
|
|
582
|
-
return post;
|
|
650
|
+
return _a.#singleExecution(post);
|
|
583
651
|
}
|
|
584
652
|
// Register the resolution waiter SYNCHRONOUSLY before any await
|
|
585
653
|
// yields. A same-tick resolveProposal() (e.g. from a test that
|
|
@@ -619,10 +687,10 @@ class Dispatcher {
|
|
|
619
687
|
});
|
|
620
688
|
const post = await this.#proposals.applyResolution(logEntryId, effective);
|
|
621
689
|
await this.#notifySettled(context, logEntryId);
|
|
622
|
-
return post;
|
|
690
|
+
return _a.#singleExecution(post);
|
|
623
691
|
}
|
|
624
692
|
await this.#notifySettled(context, logEntryId);
|
|
625
|
-
return result;
|
|
693
|
+
return _a.#singleExecution(result);
|
|
626
694
|
}
|
|
627
695
|
// {§context-fit} — a result the model asked for arrives whole when it fits the remaining budget;
|
|
628
696
|
// otherwise the row is its size and address — a READ of lines above the longest prefix of them that
|
|
@@ -724,11 +792,9 @@ class Dispatcher {
|
|
|
724
792
|
return null;
|
|
725
793
|
if (resolved.address === null)
|
|
726
794
|
return null;
|
|
727
|
-
const rendered =
|
|
728
|
-
?
|
|
729
|
-
:
|
|
730
|
-
if (rendered === null)
|
|
731
|
-
throw new TypeError("Resolved entry target did not render.");
|
|
795
|
+
const rendered = resolved.address.scheme === "file"
|
|
796
|
+
? entryAddress("file", "", resolved.address.pathname, await this.#workspaceRoot(workspaceId))
|
|
797
|
+
: renderAuthoredTarget({ ...target, fragment: null });
|
|
732
798
|
return {
|
|
733
799
|
scheme: resolved.address.scheme,
|
|
734
800
|
authority: resolved.address.authority,
|
|
@@ -1317,7 +1383,14 @@ class Dispatcher {
|
|
|
1317
1383
|
// `local` (bare path) carries no URL parts — store the raw text as the pathname for the log record, scheme=null.
|
|
1318
1384
|
if (path.kind === "local")
|
|
1319
1385
|
return { scheme: null, username: null, password: null, hostname: null, port: null, pathname: PathSyntax.decodeParens(path.raw), query: null, fragment: path.fragment ?? null }; // {§path-parentheses} {§local-path-fragment}
|
|
1320
|
-
|
|
1386
|
+
if (path.scheme === "file") {
|
|
1387
|
+
const pathname = FileAddress.pathname(path);
|
|
1388
|
+
// An invalid URL has no resolved identity; retain the refused input alongside its failure.
|
|
1389
|
+
if (typeof pathname !== "string")
|
|
1390
|
+
return { ...path };
|
|
1391
|
+
return { scheme: null, username: null, password: null, hostname: null, port: null, pathname, query: null, fragment: path.fragment };
|
|
1392
|
+
}
|
|
1393
|
+
const scheme = path.scheme;
|
|
1321
1394
|
// The registered scheme owns authority disposition. Namespace authority
|
|
1322
1395
|
// is path syntax; resource and owner authorities remain explicit in the
|
|
1323
1396
|
// durable operation evidence.
|