@axiom-lattice/gateway 4.1.2 → 4.1.4

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 CHANGED
@@ -12006,6 +12006,16 @@ function verifySignedConsoleSession(authHeader, secret) {
12006
12006
  }
12007
12007
  }
12008
12008
  var PUBLIC_ROUTES = ["/api/auth/login", "/api/auth/register", "/health", "/api/stt/models"];
12009
+ function isStaleConsoleSessionCredential(authHeader) {
12010
+ if (!authHeader?.startsWith("Bearer ")) return false;
12011
+ const parts = authHeader.substring(7).split(".");
12012
+ if (parts.length !== 2 || !/^[A-Za-z0-9_-]{43}$/.test(parts[1])) return false;
12013
+ try {
12014
+ return isConsoleSessionPayload(JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8")));
12015
+ } catch {
12016
+ return false;
12017
+ }
12018
+ }
12009
12019
  function bypassesConsoleAuth(request) {
12010
12020
  const path6 = request.url.split("?")[0];
12011
12021
  const method = request.method;
@@ -12025,7 +12035,15 @@ function registerConsoleAuthHook(app2, options) {
12025
12035
  }
12026
12036
  if (bypassesConsoleAuth(request)) return;
12027
12037
  if (session) return;
12028
- if (!options.authRequired || request.method === "OPTIONS") return;
12038
+ if (!options.authRequired || request.method === "OPTIONS") {
12039
+ if (!PUBLIC_ROUTES.some((route) => request.url === route) && !request.url.startsWith("/s/") && isStaleConsoleSessionCredential(request.headers.authorization)) {
12040
+ await reply.status(401).send({
12041
+ success: false,
12042
+ error: "Unauthorized - Session token is invalid or expired. Please sign in again."
12043
+ });
12044
+ }
12045
+ return;
12046
+ }
12029
12047
  if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
12030
12048
  if (request.url.startsWith("/s/")) return;
12031
12049
  const path6 = request.url.split("?")[0];
@@ -14181,14 +14199,6 @@ function header(request, name) {
14181
14199
  if (typeof value !== "string" || !value) throw new HttpTaskError(400, "INVALID_TASK_INPUT");
14182
14200
  return value;
14183
14201
  }
14184
- function assertHeaderScope(request, projectId, workspaceId) {
14185
- const requestedProject = header(request, "x-project-id");
14186
- const requestedWorkspace = header(request, "x-workspace-id");
14187
- const canonicalProject = projectId && projectId !== "default" ? projectId : void 0;
14188
- if (requestedProject && requestedProject !== "default" && requestedProject !== canonicalProject || requestedWorkspace !== void 0 && requestedWorkspace !== workspaceId) {
14189
- throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14190
- }
14191
- }
14192
14202
  function integer(value, minimum = 0) {
14193
14203
  if (value === void 0) return void 0;
14194
14204
  const parsed = typeof value === "number" ? value : typeof value === "string" && value ? Number(value) : NaN;
@@ -14443,27 +14453,50 @@ function createTaskController(deps) {
14443
14453
  }
14444
14454
  const metadata = query.metadata === void 0 ? void 0 : parseMetadata(query.metadata);
14445
14455
  if (!projectId || projectId === "default") {
14446
- assertHeaderScope(request);
14447
- const rows2 = await deps.tasks.list({
14456
+ const requestedWorkspace = header(request, "x-workspace-id");
14457
+ const status = typeof query.status === "string" ? query.status : void 0;
14458
+ const priority = typeof query.priority === "string" ? query.priority : void 0;
14459
+ const parentId = typeof query.parentId === "string" ? query.parentId : void 0;
14460
+ const ownerType = query.ownerType === "user" || query.ownerType === "agent" ? query.ownerType : void 0;
14461
+ const ownerId = typeof query.ownerId === "string" ? query.ownerId : void 0;
14462
+ const effectiveLimit = limit ?? 50;
14463
+ const effectiveOffset = offset ?? 0;
14464
+ const perQueryLimit = effectiveLimit + effectiveOffset;
14465
+ const queries = [{
14448
14466
  tenantId,
14449
14467
  ownerType: "user",
14450
14468
  ownerId: userId,
14451
14469
  projectId: null,
14452
- status: typeof query.status === "string" ? query.status : void 0,
14453
- priority: typeof query.priority === "string" ? query.priority : void 0,
14454
- parentId: typeof query.parentId === "string" ? query.parentId : void 0,
14470
+ status,
14471
+ priority,
14472
+ parentId,
14455
14473
  metadata,
14456
- limit,
14457
- offset
14458
- });
14474
+ limit: perQueryLimit,
14475
+ offset: 0
14476
+ }];
14477
+ if (requestedWorkspace && deps.readableProjects) {
14478
+ const readable = await deps.readableProjects({ tenantId, userId, workspaceId: requestedWorkspace });
14479
+ for (const project2 of readable) {
14480
+ queries.push({
14481
+ tenantId,
14482
+ workspaceId: project2.workspaceId,
14483
+ projectId: project2.id,
14484
+ status,
14485
+ priority,
14486
+ parentId,
14487
+ metadata,
14488
+ ownerType,
14489
+ ownerId,
14490
+ limit: perQueryLimit,
14491
+ offset: 0
14492
+ });
14493
+ }
14494
+ }
14495
+ const merged = (await Promise.all(queries.map((q) => deps.tasks.list(q)))).flat().sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
14496
+ const rows2 = merged.slice(effectiveOffset, effectiveOffset + effectiveLimit);
14459
14497
  return { success: true, data: rows2, count: rows2.length };
14460
14498
  }
14461
- const requestedProject = header(request, "x-project-id");
14462
- if (requestedProject && requestedProject !== "default" && requestedProject !== projectId) {
14463
- throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14464
- }
14465
14499
  const { project } = await authorize(request, projectId, "read");
14466
- assertHeaderScope(request, project.id, project.workspaceId);
14467
14500
  const rows = await deps.tasks.list({
14468
14501
  tenantId,
14469
14502
  workspaceId: project.workspaceId,
@@ -14483,11 +14516,6 @@ function createTaskController(deps) {
14483
14516
  await preauthorizeExplicitProject(request, authorize);
14484
14517
  const current = await deps.tasks.getById(getTenantId17(request), request.params.id);
14485
14518
  if (!current) return sendNotFound(reply);
14486
- try {
14487
- assertHeaderScope(request, current.projectId, current.workspaceId);
14488
- } catch {
14489
- throw notFound();
14490
- }
14491
14519
  await authorizeRead(request, current, authorize);
14492
14520
  return { success: true, data: current };
14493
14521
  });
@@ -14495,8 +14523,6 @@ function createTaskController(deps) {
14495
14523
  const body = snapshotCreate(request.body);
14496
14524
  const tenantId = getTenantId17(request);
14497
14525
  const userId = getUserId4(request);
14498
- const requestedProject = header(request, "x-project-id");
14499
- if (requestedProject && requestedProject !== "default" && requestedProject !== body.projectId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14500
14526
  const governed = governedCreate(body);
14501
14527
  if (governed) return reply.code(409).send({ success: false, code: governed });
14502
14528
  if (!body.projectId || body.projectId === "default") {
@@ -14506,11 +14532,8 @@ function createTaskController(deps) {
14506
14532
  }
14507
14533
  const capability2 = body.ownerType === "agent" ? "manage" : "send";
14508
14534
  const { project } = await authorize(request, body.projectId, capability2);
14509
- const requestedWorkspace = header(request, "x-workspace-id");
14510
- if (body.workspaceId !== void 0 && body.workspaceId !== project.workspaceId || requestedWorkspace !== void 0 && requestedWorkspace !== project.workspaceId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14511
14535
  return mutationMutex.runExclusive({ tenantId, projectId: project.id }, async () => {
14512
14536
  const lockedAccess = await authorize(request, project.id, capability2);
14513
- if (lockedAccess.project.workspaceId !== project.workspaceId) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14514
14537
  const candidate = {
14515
14538
  id: body.id ?? "__new_task__",
14516
14539
  tenantId,
@@ -14528,13 +14551,8 @@ function createTaskController(deps) {
14528
14551
  let ownerType = "user";
14529
14552
  let ownerId = userId;
14530
14553
  if (body.ownerType === "agent") {
14531
- const target = await deps.bots.findByAssistant(tenantId, project.id, body.ownerId ?? "");
14532
- const room = await deps.rooms.getMainRoom(tenantId, project.id);
14533
- if (!target || !room || target.status !== "active" || target.workspaceId !== project.workspaceId || target.roomId !== room.id) {
14534
- return reply.code(409).send({ success: false, code: "PROJECT_TASK_OWNER_INACTIVE" });
14535
- }
14536
14554
  ownerType = "agent";
14537
- ownerId = target.assistantId;
14555
+ ownerId = body.ownerId ?? "";
14538
14556
  }
14539
14557
  const created = await deps.tasks.create({
14540
14558
  ...body,
@@ -14558,11 +14576,6 @@ function createTaskController(deps) {
14558
14576
  await preauthorizeExplicitProject(request, authorize);
14559
14577
  const current = await deps.tasks.getById(getTenantId17(request), request.params.id);
14560
14578
  if (!current) return sendNotFound(reply);
14561
- try {
14562
- assertHeaderScope(request, current.projectId, current.workspaceId);
14563
- } catch {
14564
- throw notFound();
14565
- }
14566
14579
  await authorizeMutation(request, current, authorize);
14567
14580
  return lockedTask(request, current, async (locked) => {
14568
14581
  const governed = governedUpdate(locked, body);
@@ -14581,11 +14594,6 @@ function createTaskController(deps) {
14581
14594
  await preauthorizeExplicitProject(request, authorize);
14582
14595
  const current = await deps.tasks.getById(getTenantId17(request), request.params.id);
14583
14596
  if (!current) return sendNotFound(reply);
14584
- try {
14585
- assertHeaderScope(request, current.projectId, current.workspaceId);
14586
- } catch {
14587
- throw notFound();
14588
- }
14589
14597
  await authorizeMutation(request, current, authorize);
14590
14598
  return lockedTask(request, current, async (locked) => {
14591
14599
  if (locked.ownerType === "agent") return reply.code(403).send({ success: false, code: "AGENT_RUNTIME_REQUIRED" });
@@ -14600,11 +14608,6 @@ function createTaskController(deps) {
14600
14608
  await preauthorizeExplicitProject(request, authorize);
14601
14609
  const current = await deps.tasks.getById(getTenantId17(request), request.params.id);
14602
14610
  if (!current) return sendNotFound(reply);
14603
- try {
14604
- assertHeaderScope(request, current.projectId, current.workspaceId);
14605
- } catch {
14606
- throw notFound();
14607
- }
14608
14611
  await authorizeMutation(request, current, authorize);
14609
14612
  return lockedTask(request, current, async (locked) => {
14610
14613
  if (locked.ownerType === "agent") return reply.code(403).send({ success: false, code: "AGENT_RUNTIME_REQUIRED" });
@@ -14622,11 +14625,6 @@ function createTaskController(deps) {
14622
14625
  await preauthorizeExplicitProject(request, authorize);
14623
14626
  const current = await deps.tasks.getById(getTenantId17(request), request.params.id);
14624
14627
  if (!current) return sendNotFound(reply);
14625
- try {
14626
- assertHeaderScope(request, current.projectId, current.workspaceId);
14627
- } catch {
14628
- throw notFound();
14629
- }
14630
14628
  await authorizeRead(request, current, authorize);
14631
14629
  const query = exactObject(request.query, ["action", "limit", "offset"]);
14632
14630
  const rows = await deps.workItems.list({
@@ -14644,8 +14642,6 @@ function createTaskController(deps) {
14644
14642
  const reassign = withErrors(async (request, reply) => {
14645
14643
  const body = snapshotReassign(request.body);
14646
14644
  const pathProject = request.params.projectId;
14647
- const requestedProject = header(request, "x-project-id");
14648
- if (requestedProject && requestedProject !== "default" && requestedProject !== pathProject) throw new HttpTaskError(409, "PROJECT_TASK_SCOPE_CONFLICT");
14649
14645
  const access = await authorize(request, pathProject, "manage");
14650
14646
  const snapshot = await deps.tasks.getById(getTenantId17(request), request.params.id);
14651
14647
  if (!snapshot || snapshot.projectId !== access.project.id || snapshot.workspaceId !== access.project.workspaceId || snapshot.ownerType !== "agent") throw notFound();
@@ -18691,6 +18687,7 @@ var import_node_crypto12 = require("crypto");
18691
18687
  var import_core47 = require("@axiom-lattice/core");
18692
18688
  var SYSTEM_ACTOR2 = "system";
18693
18689
  var TERMINAL_ACTIONS = ["completed", "failed", "interrupted", "cancelled"];
18690
+ var MAX_LEARNING_ROUND_ANCESTORS = 8;
18694
18691
  var ProjectTaskObserver = class {
18695
18692
  /** @param deps Durable stores and Project Task runtime services. @param options Positive bounded scan limits. */
18696
18693
  constructor(deps, options) {
@@ -18848,6 +18845,7 @@ var ProjectTaskObserver = class {
18848
18845
  signal ?? (signal = this.deps.signal);
18849
18846
  if (signal?.aborted || this.disposed) return;
18850
18847
  if (!(0, import_core47.isProjectLifecycleTask)(task) || task.ownerType !== "agent") return;
18848
+ if (await this.belongsToLearningRound(task)) return;
18851
18849
  if (task.status === "pending") {
18852
18850
  if (await this.isReady(task) && !signal?.aborted && !this.disposed) await this.runStart(task, signal);
18853
18851
  return;
@@ -18860,6 +18858,22 @@ var ProjectTaskObserver = class {
18860
18858
  await this.resumeIfReady(task, signal);
18861
18859
  }
18862
18860
  }
18861
+ // SCOPE-COMPAT: learning rounds (metadata.trainingRound) and their subtrees execute in the
18862
+ // architect's builder thread (canvas CopilotPanel), not the Project Room runtime. Exempting
18863
+ // them here keeps admission hints, startup reconciliation, and dependent re-evaluation from
18864
+ // claiming these tasks — no roster warnings, no restart noise, and no double execution if
18865
+ // the architect later joins the room roster.
18866
+ async belongsToLearningRound(task) {
18867
+ let current = task;
18868
+ for (let depth = 0; current && depth < MAX_LEARNING_ROUND_ANCESTORS; depth += 1) {
18869
+ if (current.metadata?.trainingRound === true) return true;
18870
+ if (!current.parentId) return false;
18871
+ const parent = snapshotTaskItem(await this.deps.tasks.getById(current.tenantId, current.parentId));
18872
+ if (!parent || parent.id !== current.parentId || parent.tenantId !== current.tenantId) return false;
18873
+ current = parent;
18874
+ }
18875
+ return false;
18876
+ }
18863
18877
  /** Collects every bounded dependent ID before re-reading or scheduling any dependent. */
18864
18878
  async reevaluateDependents(task) {
18865
18879
  const { ids } = await this.collectDependentIds(task);
@@ -18965,6 +18979,7 @@ var ProjectTaskObserver = class {
18965
18979
  }
18966
18980
  async wakeParent(child) {
18967
18981
  if (!child.parentId) return;
18982
+ if (await this.belongsToLearningRound(child)) return;
18968
18983
  const parent = snapshotTaskItem(await this.deps.tasks.getById(child.tenantId, child.parentId));
18969
18984
  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;
18970
18985
  const interruption = (0, import_core47.parseExternalDependency)(parent.context?.interruption);
@@ -19428,7 +19443,7 @@ var RoomProjectionService = class {
19428
19443
  * @returns Counts for inspected rows, projections, warnings, and bound truncation.
19429
19444
  */
19430
19445
  async repairProject(input) {
19431
- const result = { inspected: 0, projected: 0, warnings: 0, truncated: false };
19446
+ const result = { inspected: 0, projected: 0, warnings: 0, skipped: 0, truncated: false };
19432
19447
  let before;
19433
19448
  for (let page = 0; page < input.maxPages; page += 1) {
19434
19449
  const rows = await this.deps.events.listProjectLifecycleEvents({
@@ -19454,10 +19469,15 @@ var RoomProjectionService = class {
19454
19469
  if (safeIdentity) await this.warning("INVALID_PROJECTABLE_EVENT", safeIdentity, input);
19455
19470
  continue;
19456
19471
  }
19472
+ let stored;
19473
+ try {
19474
+ stored = await this.deps.tasks.getById(input.tenantId, row.taskId);
19475
+ } catch {
19476
+ stored = void 0;
19477
+ }
19457
19478
  let task;
19458
19479
  try {
19459
- const stored = await this.deps.tasks.getById(input.tenantId, row.taskId);
19460
- task = stored === null ? void 0 : snapshotProjectTask(stored);
19480
+ task = stored === null || stored === void 0 ? void 0 : snapshotTaskItem(stored);
19461
19481
  } catch {
19462
19482
  task = void 0;
19463
19483
  }
@@ -19466,6 +19486,10 @@ var RoomProjectionService = class {
19466
19486
  await this.warning("INVALID_PROJECTABLE_EVENT", { taskId: row.taskId, eventKey: row.eventKey }, input);
19467
19487
  continue;
19468
19488
  }
19489
+ if (task.ownerType !== "agent" || !(0, import_core48.isProjectLifecycleTask)(task)) {
19490
+ result.skipped += 1;
19491
+ continue;
19492
+ }
19469
19493
  const projected = await this.project({ task, room: input.room, workItem: row });
19470
19494
  if (projected.kind === "projected") result.projected += 1;
19471
19495
  else result.warnings += 1;
@@ -21259,6 +21283,18 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
21259
21283
  projectMutationMutex: import_core51.projectMutationMutex,
21260
21284
  executionMutex: projectTaskComposition?.mutex ?? {
21261
21285
  runExclusive: (_scope, operation) => operation()
21286
+ },
21287
+ readableProjects: async ({ tenantId, userId, workspaceId }) => {
21288
+ const projects = await projectStore.getProjectsByWorkspace(tenantId, workspaceId);
21289
+ const readable = [];
21290
+ for (const project of projects) {
21291
+ try {
21292
+ await accessService.requireExistingProjectAccess({ tenantId, userId, projectId: project.id, capability: "read" });
21293
+ readable.push({ id: project.id, workspaceId: project.workspaceId });
21294
+ } catch {
21295
+ }
21296
+ }
21297
+ return readable;
21262
21298
  }
21263
21299
  }));
21264
21300
  app2.get(