feat(foundry): complete HGeoZone live layer
This commit is contained in:
@@ -17,6 +17,7 @@ import { createFoundryAgentStore } from "./foundry-agent-store.mjs";
|
||||
import { createFoundryDataProductConsumerManager } from "./foundry-data-product-consumer.mjs";
|
||||
import { createFoundryReaderGrantProvisioner } from "./foundry-reader-grant-provisioner.mjs";
|
||||
import { handleFoundryEntitlementRequest, handleFoundryMcpRequest } from "./foundry-mcp.mjs";
|
||||
import { isMapLiveDataSlot } from "./map-live-data-slot.mjs";
|
||||
import { normalizeMapPresentationProfile, normalizeMapPresentationProfiles } from "./map-presentation-profile.mjs";
|
||||
import { createFoundryAuth } from "./nodedc-auth.mjs";
|
||||
|
||||
@@ -1049,7 +1050,7 @@ async function resolveRuntimeMapDataProductBinding(applicationId, pageId, bindin
|
||||
const binding = layout.dataProductBindings.find((candidate) => candidate.id === bindingId);
|
||||
if (!binding) throw applicationError("data_product_binding_not_found", 404);
|
||||
const slot = template?.slots?.find((candidate) => candidate.id === binding.slotId);
|
||||
if (!slot || slot.kind !== "entity-stream") throw applicationError("map_entity_stream_slot_required", 409);
|
||||
if (!isMapLiveDataSlot(slot)) throw applicationError("map_live_data_slot_required", 409);
|
||||
return { application, page, binding };
|
||||
}
|
||||
|
||||
@@ -2076,7 +2077,7 @@ const foundryMcpOperations = {
|
||||
if (page.template?.id !== "map") throw applicationError("map_page_required");
|
||||
const template = findPageTemplate(page.template.id, page.template.version);
|
||||
const slot = template?.slots?.find((candidate) => candidate.id === binding.slotId);
|
||||
if (!slot || slot.kind !== "entity-stream") throw applicationError("map_entity_stream_slot_required");
|
||||
if (!isMapLiveDataSlot(slot)) throw applicationError("map_live_data_slot_required");
|
||||
const layout = validateMapPageLayout(page.layout?.map || defaultMapPageLayout());
|
||||
const existingBinding = layout.dataProductBindings.find((item) => item.id === binding.id);
|
||||
const resolvedBinding = {
|
||||
|
||||
@@ -678,7 +678,7 @@ function zoneV2Target(fieldProjection = zoneV2Projection) {
|
||||
binding: {
|
||||
id: "depttrans-pmd-slow-zones",
|
||||
dataProductId: zoneV2Product.id,
|
||||
slotId: "points",
|
||||
slotId: "zones",
|
||||
delivery: "snapshot+patch",
|
||||
semanticTypes: ["map.zone"],
|
||||
fieldProjection: [...fieldProjection],
|
||||
|
||||
@@ -608,7 +608,7 @@ const tools = [
|
||||
{
|
||||
name: "foundry_upsert_map_data_product_binding",
|
||||
title: "Upsert Map data product binding",
|
||||
description: "Bind one approved provider-neutral data product to an entity-stream slot of a Map Page instance. The binding stores no provider transport, endpoint, tenant, connection or credential.",
|
||||
description: "Bind one approved provider-neutral data product to a live-data slot (points or zones) of a Map Page instance. The binding stores no provider transport, endpoint, tenant, connection or credential.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
@@ -625,7 +625,7 @@ const tools = [
|
||||
displayName: { type: "string", minLength: 1, maxLength: 120, description: "User-defined text label shown in the Objects menu and window header." },
|
||||
order: { type: "integer", minimum: 0, maximum: 10000, description: "Application composition order inside the Objects menu." },
|
||||
dataProductId: { type: "string", description: "Versioned provider-neutral product, for example fleet.positions.current.v1." },
|
||||
slotId: { type: "string", description: "Approved Map Page entity-stream slot." },
|
||||
slotId: { type: "string", description: "Approved Map Page live-data slot (entity-stream or zone-stream)." },
|
||||
semanticTypes: { type: "array", items: { type: "string" } },
|
||||
fieldProjection: { type: "array", items: { type: "string" } },
|
||||
presentationProfileId: { type: "string", description: "Existing page-owned provider-neutral Map presentation profile id." },
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const MAP_LIVE_DATA_SLOT_KINDS = new Set(["entity-stream", "zone-stream"]);
|
||||
|
||||
/**
|
||||
* Map runtime bindings may only target slots that deliver canonical live
|
||||
* entity facts. Point entities and zones have distinct template slots, but
|
||||
* share the same provider-neutral snapshot/patch transport contract.
|
||||
*/
|
||||
export function isMapLiveDataSlot(slot) {
|
||||
return Boolean(slot && MAP_LIVE_DATA_SLOT_KINDS.has(slot.kind));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { isMapLiveDataSlot } from "./map-live-data-slot.mjs";
|
||||
|
||||
test("point and zone streams are approved Map live-data slots", () => {
|
||||
assert.equal(isMapLiveDataSlot({ id: "points", kind: "entity-stream" }), true);
|
||||
assert.equal(isMapLiveDataSlot({ id: "zones", kind: "zone-stream" }), true);
|
||||
});
|
||||
|
||||
test("non-entity Map slots cannot receive a data-product binding", () => {
|
||||
for (const kind of ["trace-stream", "route-stream", "selection", "command", "assistant", undefined]) {
|
||||
assert.equal(isMapLiveDataSlot(kind ? { id: "other", kind } : null), false);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user