@axiom-lattice/gateway 4.1.2 → 4.1.3
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/index.js +99 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -63
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -8750,6 +8750,16 @@ function verifySignedConsoleSession(authHeader, secret) {
|
|
|
8750
8750
|
}
|
|
8751
8751
|
}
|
|
8752
8752
|
var PUBLIC_ROUTES = ["/api/auth/login", "/api/auth/register", "/health", "/api/stt/models"];
|
|
8753
|
+
function isStaleConsoleSessionCredential(authHeader) {
|
|
8754
|
+
if (!authHeader?.startsWith("Bearer ")) return false;
|
|
8755
|
+
const parts = authHeader.substring(7).split(".");
|
|
8756
|
+
if (parts.length !== 2 || !/^[A-Za-z0-9_-]{43}$/.test(parts[1])) return false;
|
|
8757
|
+
try {
|
|
8758
|
+
return isConsoleSessionPayload(JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8")));
|
|
8759
|
+
} catch {
|
|
8760
|
+
return false;
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8753
8763
|
function bypassesConsoleAuth(request) {
|
|
8754
8764
|
const path4 = request.url.split("?")[0];
|
|
8755
8765
|
const method = request.method;
|
|
@@ -8769,7 +8779,15 @@ function registerConsoleAuthHook(app2, options) {
|
|
|
8769
8779
|
}
|
|
8770
8780
|
if (bypassesConsoleAuth(request)) return;
|
|
8771
8781
|
if (session) return;
|
|
8772
|
-
if (!options.authRequired || request.method === "OPTIONS")
|
|
8782
|
+
if (!options.authRequired || request.method === "OPTIONS") {
|
|
8783
|
+
if (!PUBLIC_ROUTES.some((route) => request.url === route) && !request.url.startsWith("/s/") && isStaleConsoleSessionCredential(request.headers.authorization)) {
|
|
8784
|
+
await reply.status(401).send({
|
|
8785
|
+
success: false,
|
|
8786
|
+
error: "Unauthorized - Session token is invalid or expired. Please sign in again."
|
|
8787
|
+
});
|
|
8788
|
+
}
|
|
8789
|
+
return;
|
|
8790
|
+
}
|
|
8773
8791
|
if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
|
|
8774
8792
|
if (request.url.startsWith("/s/")) return;
|
|
8775
8793
|
const path4 = request.url.split("?")[0];
|
|
@@ -10948,14 +10966,6 @@ function header(request, name) {
|
|
|
10948
10966
|
if (typeof value !== "string" || !value) throw new HttpTaskError(400, "INVALID_TASK_INPUT");
|
|
10949
10967
|
return value;
|
|
10950
10968
|
}
|
|
10951
|
-
function assertHeaderScope(request, projectId, workspaceId) {
|
|
10952
|
-
const requestedProject = header(request, "x-project-id");
|
|
10953
|
-
const requestedWorkspace = header(request, "x-workspace-id");
|
|
10954
|
-
const canonicalProject = projectId && projectId !== "default" ? projectId : void 0;
|
|
10955
|
-
if (requestedProject && requestedProject !== "default" && requestedProject !== canonicalProject || requestedWorkspace !== void 0 && requestedWorkspace !== workspaceId) {
|
|
10956
|
-
throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
10957
|
-
}
|
|
10958
|
-
}
|
|
10959
10969
|
function integer(value, minimum = 0) {
|
|
10960
10970
|
if (value === void 0) return void 0;
|
|
10961
10971
|
const parsed = typeof value === "number" ? value : typeof value === "string" && value ? Number(value) : NaN;
|
|
@@ -11210,27 +11220,50 @@ function createTaskController(deps) {
|
|
|
11210
11220
|
}
|
|
11211
11221
|
const metadata = query.metadata === void 0 ? void 0 : parseMetadata(query.metadata);
|
|
11212
11222
|
if (!projectId || projectId === "default") {
|
|
11213
|
-
|
|
11214
|
-
const
|
|
11223
|
+
const requestedWorkspace = header(request, "x-workspace-id");
|
|
11224
|
+
const status = typeof query.status === "string" ? query.status : void 0;
|
|
11225
|
+
const priority = typeof query.priority === "string" ? query.priority : void 0;
|
|
11226
|
+
const parentId = typeof query.parentId === "string" ? query.parentId : void 0;
|
|
11227
|
+
const ownerType = query.ownerType === "user" || query.ownerType === "agent" ? query.ownerType : void 0;
|
|
11228
|
+
const ownerId = typeof query.ownerId === "string" ? query.ownerId : void 0;
|
|
11229
|
+
const effectiveLimit = limit ?? 50;
|
|
11230
|
+
const effectiveOffset = offset ?? 0;
|
|
11231
|
+
const perQueryLimit = effectiveLimit + effectiveOffset;
|
|
11232
|
+
const queries = [{
|
|
11215
11233
|
tenantId,
|
|
11216
11234
|
ownerType: "user",
|
|
11217
11235
|
ownerId: userId,
|
|
11218
11236
|
projectId: null,
|
|
11219
|
-
status
|
|
11220
|
-
priority
|
|
11221
|
-
parentId
|
|
11237
|
+
status,
|
|
11238
|
+
priority,
|
|
11239
|
+
parentId,
|
|
11222
11240
|
metadata,
|
|
11223
|
-
limit,
|
|
11224
|
-
offset
|
|
11225
|
-
}
|
|
11241
|
+
limit: perQueryLimit,
|
|
11242
|
+
offset: 0
|
|
11243
|
+
}];
|
|
11244
|
+
if (requestedWorkspace && deps.readableProjects) {
|
|
11245
|
+
const readable = await deps.readableProjects({ tenantId, userId, workspaceId: requestedWorkspace });
|
|
11246
|
+
for (const project2 of readable) {
|
|
11247
|
+
queries.push({
|
|
11248
|
+
tenantId,
|
|
11249
|
+
workspaceId: project2.workspaceId,
|
|
11250
|
+
projectId: project2.id,
|
|
11251
|
+
status,
|
|
11252
|
+
priority,
|
|
11253
|
+
parentId,
|
|
11254
|
+
metadata,
|
|
11255
|
+
ownerType,
|
|
11256
|
+
ownerId,
|
|
11257
|
+
limit: perQueryLimit,
|
|
11258
|
+
offset: 0
|
|
11259
|
+
});
|
|
11260
|
+
}
|
|
11261
|
+
}
|
|
11262
|
+
const merged = (await Promise.all(queries.map((q) => deps.tasks.list(q)))).flat().sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
|
|
11263
|
+
const rows2 = merged.slice(effectiveOffset, effectiveOffset + effectiveLimit);
|
|
11226
11264
|
return { success: true, data: rows2, count: rows2.length };
|
|
11227
11265
|
}
|
|
11228
|
-
const requestedProject = header(request, "x-project-id");
|
|
11229
|
-
if (requestedProject && requestedProject !== "default" && requestedProject !== projectId) {
|
|
11230
|
-
throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
11231
|
-
}
|
|
11232
11266
|
const { project } = await authorize(request, projectId, "read");
|
|
11233
|
-
assertHeaderScope(request, project.id, project.workspaceId);
|
|
11234
11267
|
const rows = await deps.tasks.list({
|
|
11235
11268
|
tenantId,
|
|
11236
11269
|
workspaceId: project.workspaceId,
|
|
@@ -11250,11 +11283,6 @@ function createTaskController(deps) {
|
|
|
11250
11283
|
await preauthorizeExplicitProject(request, authorize);
|
|
11251
11284
|
const current = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11252
11285
|
if (!current) return sendNotFound(reply);
|
|
11253
|
-
try {
|
|
11254
|
-
assertHeaderScope(request, current.projectId, current.workspaceId);
|
|
11255
|
-
} catch {
|
|
11256
|
-
throw notFound();
|
|
11257
|
-
}
|
|
11258
11286
|
await authorizeRead(request, current, authorize);
|
|
11259
11287
|
return { success: true, data: current };
|
|
11260
11288
|
});
|
|
@@ -11262,8 +11290,6 @@ function createTaskController(deps) {
|
|
|
11262
11290
|
const body = snapshotCreate(request.body);
|
|
11263
11291
|
const tenantId = getTenantId16(request);
|
|
11264
11292
|
const userId = getUserId4(request);
|
|
11265
|
-
const requestedProject = header(request, "x-project-id");
|
|
11266
|
-
if (requestedProject && requestedProject !== "default" && requestedProject !== body.projectId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
11267
11293
|
const governed = governedCreate(body);
|
|
11268
11294
|
if (governed) return reply.code(409).send({ success: false, code: governed });
|
|
11269
11295
|
if (!body.projectId || body.projectId === "default") {
|
|
@@ -11273,11 +11299,8 @@ function createTaskController(deps) {
|
|
|
11273
11299
|
}
|
|
11274
11300
|
const capability2 = body.ownerType === "agent" ? "manage" : "send";
|
|
11275
11301
|
const { project } = await authorize(request, body.projectId, capability2);
|
|
11276
|
-
const requestedWorkspace = header(request, "x-workspace-id");
|
|
11277
|
-
if (body.workspaceId !== void 0 && body.workspaceId !== project.workspaceId || requestedWorkspace !== void 0 && requestedWorkspace !== project.workspaceId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
11278
11302
|
return mutationMutex.runExclusive({ tenantId, projectId: project.id }, async () => {
|
|
11279
11303
|
const lockedAccess = await authorize(request, project.id, capability2);
|
|
11280
|
-
if (lockedAccess.project.workspaceId !== project.workspaceId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
11281
11304
|
const candidate = {
|
|
11282
11305
|
id: body.id ?? "__new_task__",
|
|
11283
11306
|
tenantId,
|
|
@@ -11295,13 +11318,8 @@ function createTaskController(deps) {
|
|
|
11295
11318
|
let ownerType = "user";
|
|
11296
11319
|
let ownerId = userId;
|
|
11297
11320
|
if (body.ownerType === "agent") {
|
|
11298
|
-
const target = await deps.bots.findByAssistant(tenantId, project.id, body.ownerId ?? "");
|
|
11299
|
-
const room = await deps.rooms.getMainRoom(tenantId, project.id);
|
|
11300
|
-
if (!target || !room || target.status !== "active" || target.workspaceId !== project.workspaceId || target.roomId !== room.id) {
|
|
11301
|
-
return reply.code(409).send({ success: false, code: "PROJECT_TASK_OWNER_INACTIVE" });
|
|
11302
|
-
}
|
|
11303
11321
|
ownerType = "agent";
|
|
11304
|
-
ownerId =
|
|
11322
|
+
ownerId = body.ownerId ?? "";
|
|
11305
11323
|
}
|
|
11306
11324
|
const created = await deps.tasks.create({
|
|
11307
11325
|
...body,
|
|
@@ -11325,11 +11343,6 @@ function createTaskController(deps) {
|
|
|
11325
11343
|
await preauthorizeExplicitProject(request, authorize);
|
|
11326
11344
|
const current = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11327
11345
|
if (!current) return sendNotFound(reply);
|
|
11328
|
-
try {
|
|
11329
|
-
assertHeaderScope(request, current.projectId, current.workspaceId);
|
|
11330
|
-
} catch {
|
|
11331
|
-
throw notFound();
|
|
11332
|
-
}
|
|
11333
11346
|
await authorizeMutation(request, current, authorize);
|
|
11334
11347
|
return lockedTask(request, current, async (locked) => {
|
|
11335
11348
|
const governed = governedUpdate(locked, body);
|
|
@@ -11348,11 +11361,6 @@ function createTaskController(deps) {
|
|
|
11348
11361
|
await preauthorizeExplicitProject(request, authorize);
|
|
11349
11362
|
const current = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11350
11363
|
if (!current) return sendNotFound(reply);
|
|
11351
|
-
try {
|
|
11352
|
-
assertHeaderScope(request, current.projectId, current.workspaceId);
|
|
11353
|
-
} catch {
|
|
11354
|
-
throw notFound();
|
|
11355
|
-
}
|
|
11356
11364
|
await authorizeMutation(request, current, authorize);
|
|
11357
11365
|
return lockedTask(request, current, async (locked) => {
|
|
11358
11366
|
if (locked.ownerType === "agent") return reply.code(403).send({ success: false, code: "AGENT_RUNTIME_REQUIRED" });
|
|
@@ -11367,11 +11375,6 @@ function createTaskController(deps) {
|
|
|
11367
11375
|
await preauthorizeExplicitProject(request, authorize);
|
|
11368
11376
|
const current = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11369
11377
|
if (!current) return sendNotFound(reply);
|
|
11370
|
-
try {
|
|
11371
|
-
assertHeaderScope(request, current.projectId, current.workspaceId);
|
|
11372
|
-
} catch {
|
|
11373
|
-
throw notFound();
|
|
11374
|
-
}
|
|
11375
11378
|
await authorizeMutation(request, current, authorize);
|
|
11376
11379
|
return lockedTask(request, current, async (locked) => {
|
|
11377
11380
|
if (locked.ownerType === "agent") return reply.code(403).send({ success: false, code: "AGENT_RUNTIME_REQUIRED" });
|
|
@@ -11389,11 +11392,6 @@ function createTaskController(deps) {
|
|
|
11389
11392
|
await preauthorizeExplicitProject(request, authorize);
|
|
11390
11393
|
const current = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11391
11394
|
if (!current) return sendNotFound(reply);
|
|
11392
|
-
try {
|
|
11393
|
-
assertHeaderScope(request, current.projectId, current.workspaceId);
|
|
11394
|
-
} catch {
|
|
11395
|
-
throw notFound();
|
|
11396
|
-
}
|
|
11397
11395
|
await authorizeRead(request, current, authorize);
|
|
11398
11396
|
const query = exactObject(request.query, ["action", "limit", "offset"]);
|
|
11399
11397
|
const rows = await deps.workItems.list({
|
|
@@ -11411,8 +11409,6 @@ function createTaskController(deps) {
|
|
|
11411
11409
|
const reassign = withErrors(async (request, reply) => {
|
|
11412
11410
|
const body = snapshotReassign(request.body);
|
|
11413
11411
|
const pathProject = request.params.projectId;
|
|
11414
|
-
const requestedProject = header(request, "x-project-id");
|
|
11415
|
-
if (requestedProject && requestedProject !== "default" && requestedProject !== pathProject) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
|
|
11416
11412
|
const access = await authorize(request, pathProject, "manage");
|
|
11417
11413
|
const snapshot = await deps.tasks.getById(getTenantId16(request), request.params.id);
|
|
11418
11414
|
if (!snapshot || snapshot.projectId !== access.project.id || snapshot.workspaceId !== access.project.workspaceId || snapshot.ownerType !== "agent") throw notFound();
|
|
@@ -15471,6 +15467,7 @@ import {
|
|
|
15471
15467
|
} from "@axiom-lattice/core";
|
|
15472
15468
|
var SYSTEM_ACTOR2 = "system";
|
|
15473
15469
|
var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
|
|
15470
|
+
var MAX_LEARNING_ROUND_ANCESTORS = 8;
|
|
15474
15471
|
var ProjectTaskObserver = class {
|
|
15475
15472
|
/** @param deps Durable stores and Project Task runtime services. @param options Positive bounded scan limits. */
|
|
15476
15473
|
constructor(deps, options) {
|
|
@@ -15628,6 +15625,7 @@ var ProjectTaskObserver = class {
|
|
|
15628
15625
|
signal ?? (signal = this.deps.signal);
|
|
15629
15626
|
if (signal?.aborted || this.disposed) return;
|
|
15630
15627
|
if (!isProjectLifecycleTask4(task) || task.ownerType !== "agent") return;
|
|
15628
|
+
if (await this.belongsToLearningRound(task)) return;
|
|
15631
15629
|
if (task.status === "pending") {
|
|
15632
15630
|
if (await this.isReady(task) && !signal?.aborted && !this.disposed) await this.runStart(task, signal);
|
|
15633
15631
|
return;
|
|
@@ -15640,6 +15638,22 @@ var ProjectTaskObserver = class {
|
|
|
15640
15638
|
await this.resumeIfReady(task, signal);
|
|
15641
15639
|
}
|
|
15642
15640
|
}
|
|
15641
|
+
// SCOPE-COMPAT: learning rounds (metadata.trainingRound) and their subtrees execute in the
|
|
15642
|
+
// architect's builder thread (canvas CopilotPanel), not the Project Room runtime. Exempting
|
|
15643
|
+
// them here keeps admission hints, startup reconciliation, and dependent re-evaluation from
|
|
15644
|
+
// claiming these tasks — no roster warnings, no restart noise, and no double execution if
|
|
15645
|
+
// the architect later joins the room roster.
|
|
15646
|
+
async belongsToLearningRound(task) {
|
|
15647
|
+
let current = task;
|
|
15648
|
+
for (let depth = 0; current && depth < MAX_LEARNING_ROUND_ANCESTORS; depth += 1) {
|
|
15649
|
+
if (current.metadata?.trainingRound === true) return true;
|
|
15650
|
+
if (!current.parentId) return false;
|
|
15651
|
+
const parent = snapshotTaskItem(await this.deps.tasks.getById(current.tenantId, current.parentId));
|
|
15652
|
+
if (!parent || parent.id !== current.parentId || parent.tenantId !== current.tenantId) return false;
|
|
15653
|
+
current = parent;
|
|
15654
|
+
}
|
|
15655
|
+
return false;
|
|
15656
|
+
}
|
|
15643
15657
|
/** Collects every bounded dependent ID before re-reading or scheduling any dependent. */
|
|
15644
15658
|
async reevaluateDependents(task) {
|
|
15645
15659
|
const { ids } = await this.collectDependentIds(task);
|
|
@@ -15745,6 +15759,7 @@ var ProjectTaskObserver = class {
|
|
|
15745
15759
|
}
|
|
15746
15760
|
async wakeParent(child) {
|
|
15747
15761
|
if (!child.parentId) return;
|
|
15762
|
+
if (await this.belongsToLearningRound(child)) return;
|
|
15748
15763
|
const parent = snapshotTaskItem(await this.deps.tasks.getById(child.tenantId, child.parentId));
|
|
15749
15764
|
if (!parent || parent.id !== child.parentId || child.parentId !== parent.id || parent.ownerType !== "agent" || parent.workspaceId !== child.workspaceId || parent.projectId !== child.projectId || parent.status !== "interrupted" || !parent.dependencies?.includes(child.id)) return;
|
|
15750
15765
|
const interruption = parseExternalDependency2(parent.context?.interruption);
|
|
@@ -16212,7 +16227,7 @@ var RoomProjectionService = class {
|
|
|
16212
16227
|
* @returns Counts for inspected rows, projections, warnings, and bound truncation.
|
|
16213
16228
|
*/
|
|
16214
16229
|
async repairProject(input) {
|
|
16215
|
-
const result = { inspected: 0, projected: 0, warnings: 0, truncated: false };
|
|
16230
|
+
const result = { inspected: 0, projected: 0, warnings: 0, skipped: 0, truncated: false };
|
|
16216
16231
|
let before;
|
|
16217
16232
|
for (let page = 0; page < input.maxPages; page += 1) {
|
|
16218
16233
|
const rows = await this.deps.events.listProjectLifecycleEvents({
|
|
@@ -16238,10 +16253,15 @@ var RoomProjectionService = class {
|
|
|
16238
16253
|
if (safeIdentity) await this.warning("INVALID_PROJECTABLE_EVENT", safeIdentity, input);
|
|
16239
16254
|
continue;
|
|
16240
16255
|
}
|
|
16256
|
+
let stored;
|
|
16257
|
+
try {
|
|
16258
|
+
stored = await this.deps.tasks.getById(input.tenantId, row.taskId);
|
|
16259
|
+
} catch {
|
|
16260
|
+
stored = void 0;
|
|
16261
|
+
}
|
|
16241
16262
|
let task;
|
|
16242
16263
|
try {
|
|
16243
|
-
|
|
16244
|
-
task = stored === null ? void 0 : snapshotProjectTask(stored);
|
|
16264
|
+
task = stored === null || stored === void 0 ? void 0 : snapshotTaskItem(stored);
|
|
16245
16265
|
} catch {
|
|
16246
16266
|
task = void 0;
|
|
16247
16267
|
}
|
|
@@ -16250,6 +16270,10 @@ var RoomProjectionService = class {
|
|
|
16250
16270
|
await this.warning("INVALID_PROJECTABLE_EVENT", { taskId: row.taskId, eventKey: row.eventKey }, input);
|
|
16251
16271
|
continue;
|
|
16252
16272
|
}
|
|
16273
|
+
if (task.ownerType !== "agent" || !isProjectLifecycleTask5(task)) {
|
|
16274
|
+
result.skipped += 1;
|
|
16275
|
+
continue;
|
|
16276
|
+
}
|
|
16253
16277
|
const projected = await this.project({ task, room: input.room, workItem: row });
|
|
16254
16278
|
if (projected.kind === "projected") result.projected += 1;
|
|
16255
16279
|
else result.warnings += 1;
|
|
@@ -18052,6 +18076,18 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
18052
18076
|
projectMutationMutex: projectMutationMutex2,
|
|
18053
18077
|
executionMutex: projectTaskComposition?.mutex ?? {
|
|
18054
18078
|
runExclusive: (_scope, operation) => operation()
|
|
18079
|
+
},
|
|
18080
|
+
readableProjects: async ({ tenantId, userId, workspaceId }) => {
|
|
18081
|
+
const projects = await projectStore.getProjectsByWorkspace(tenantId, workspaceId);
|
|
18082
|
+
const readable = [];
|
|
18083
|
+
for (const project of projects) {
|
|
18084
|
+
try {
|
|
18085
|
+
await accessService.requireExistingProjectAccess({ tenantId, userId, projectId: project.id, capability: "read" });
|
|
18086
|
+
readable.push({ id: project.id, workspaceId: project.workspaceId });
|
|
18087
|
+
} catch {
|
|
18088
|
+
}
|
|
18089
|
+
}
|
|
18090
|
+
return readable;
|
|
18055
18091
|
}
|
|
18056
18092
|
}));
|
|
18057
18093
|
app2.get(
|