feat(foundry): add map zones v2 consumer policy

This commit is contained in:
Codex
2026-07-21 15:20:55 +03:00
parent aa681ca069
commit de060c105b
4 changed files with 421 additions and 0 deletions
@@ -614,3 +614,251 @@ test("v4 consumer rejects bindings that omit signal_state and snapshots outside
await rm(invalidStateDir, { recursive: true, force: true });
}
});
const zoneV2Projection = [
"display_name",
"geometry_kind",
"max_speed_kph",
"schedule_timezone",
"applies_to_couriers",
"applies_to_kicksharing",
];
const zoneV2Policy = {
id: "map-zone-current-v2",
version: "2.0.0",
dataProductId: "map.zones.current.v2",
productVersion: "2.0.0",
freshness: "none",
staleAfterMs: null,
terminalStatuses: [],
consumerContract: {
ontologyRevision: "ontology.map.zone.v1",
deliveryMode: "snapshot+patch",
semanticTypes: ["map.zone"],
fieldProjection: zoneV2Projection,
fieldTypes: {
display_name: "string",
geometry_kind: "string",
max_speed_kph: "number",
schedule_timezone: "string",
applies_to_couriers: "boolean",
applies_to_kicksharing: "boolean",
},
geometryTypes: ["Polygon", "MultiPolygon"],
subjectIdentity: "semantic-type+source-id",
snapshotMode: "atomic-replace",
patchMode: "atomic",
},
removeMode: "canonical-tombstone-or-snapshot-rebase",
};
const zoneV2Product = {
id: "map.zones.current.v2",
version: "2.0.0",
ontologyRevision: "ontology.map.zone.v1",
deliveryMode: "snapshot+patch",
semanticTypes: ["map.zone"],
fields: [...zoneV2Projection, "geometry", "source_revision"],
fieldContracts: {
display_name: { type: "string", required: true },
geometry_kind: { type: "string", required: true },
max_speed_kph: { type: "number", required: false },
schedule_timezone: { type: "string", required: false },
applies_to_couriers: { type: "boolean", required: false },
applies_to_kicksharing: { type: "boolean", required: false },
},
active: true,
};
function zoneV2Target(fieldProjection = zoneV2Projection) {
return {
application: { id: "55555555-5555-4555-8555-555555555555" },
page: { id: "map" },
binding: {
id: "depttrans-pmd-slow-zones",
dataProductId: zoneV2Product.id,
slotId: "points",
delivery: "snapshot+patch",
semanticTypes: ["map.zone"],
fieldProjection: [...fieldProjection],
},
};
}
function zoneFact({ sourceId = "depttrans-zone-001", geometryType = "Polygon", maxSpeedKph = 20 } = {}) {
const polygon = [[[37.60, 55.75], [37.61, 55.75], [37.61, 55.76], [37.60, 55.75]]];
return {
sourceId,
semanticType: "map.zone",
observedAt: "2026-07-21T10:00:00.000Z",
receivedAt: "2026-07-21T10:00:01.000Z",
attributes: {
display_name: "Slow zone 001",
geometry_kind: "polygon",
max_speed_kph: maxSpeedKph,
schedule_timezone: "Europe/Moscow",
applies_to_couriers: true,
applies_to_kicksharing: true,
},
geometry: geometryType === "MultiPolygon"
? { type: "MultiPolygon", coordinates: [polygon] }
: geometryType === "Polygon"
? { type: "Polygon", coordinates: polygon }
: { type: geometryType, coordinates: [37.61, 55.75] },
};
}
function zoneSnapshot({ cursor = "903", version = "2.0.0", facts = [zoneFact()] } = {}) {
return {
schemaVersion: "nodedc.data-product.snapshot/v1",
dataProduct: { id: zoneV2Product.id, version },
generatedAt: "2026-07-21T10:00:02.000Z",
cursor,
facts,
};
}
function zonePatch(cursor, previousCursor, operations, version = "2.0.0") {
return {
schemaVersion: "nodedc.data-product.patch/v1",
dataProduct: { id: zoneV2Product.id, version },
cursor,
previousCursor,
emittedAt: "2026-07-21T10:00:03.000Z",
operations,
};
}
test("zone v2 consumer pins provider-neutral contract and commits Polygon/MultiPolygon patches by stable subject identity", async () => {
const stateDir = await mkdtemp(join(tmpdir(), "foundry-consumer-zone-v2-"));
const currentTarget = zoneV2Target();
const streamCounters = { open: 0, closed: 0 };
const manager = createFoundryDataProductConsumerManager({
stateDir,
dataPlaneUrl: "http://edp.test",
resolveTarget: async () => structuredClone(currentTarget),
readReaderToken: async () => "ndc_edprb_zone-reader-capability",
inspectReaderGrant: async () => ({ product: zoneV2Product, readerGrantAction: "reuse", readerGrantGeneration: 1 }),
resolvePolicy: () => zoneV2Policy,
sanitizeSnapshot: (value) => value,
sanitizePatch: (value) => value,
fetchImpl: async () => Response.json(zoneSnapshot()),
openStream: async ({ signal }) => sseResponse(
zonePatch("904", "903", [{ op: "upsert", fact: zoneFact({ geometryType: "MultiPolygon", maxSpeedKph: 15 }) }]),
signal,
streamCounters,
),
now: () => Date.parse("2026-07-21T10:00:10.000Z"),
idleStopMs: 10,
reconnectMinMs: 10,
reconnectMaxMs: 20,
});
try {
const input = { applicationId: currentTarget.application.id, pageId: currentTarget.page.id, bindingId: currentTarget.binding.id };
const plan = await manager.plan(input);
assert.equal(plan.configuration.product.version, "2.0.0");
assert.deepEqual(plan.configuration.policy.consumerContract.semanticTypes, ["map.zone"]);
assert.deepEqual(plan.configuration.policy.consumerContract.fieldProjection, zoneV2Projection);
assert.deepEqual(plan.configuration.policy.consumerContract.geometryTypes, ["Polygon", "MultiPolygon"]);
assert.equal(JSON.stringify(plan).includes("http://edp.test"), false);
assert.equal(JSON.stringify(plan).includes("ndc_edprb_"), false);
const applied = await manager.apply({ ...input, planId: plan.planId });
assert.equal(applied.consumer.cursor, "903");
assert.equal(applied.consumer.subjectCount, 1);
assert.equal(applied.consumer.subjects[0].subjectId, "depttrans-zone-001");
assert.equal(applied.consumer.subjects[0].status, "active");
assert.equal(applied.consumer.metrics.staleTransitions, 0);
const lease = await manager.subscribe({ ...currentTarget, after: "903" }, () => undefined);
await waitUntil(async () => (await manager.status(input)).consumer.cursor === "904", "zone_patch_cursor_904");
const patched = await manager.snapshot(currentTarget);
assert.equal(patched.facts.length, 1);
assert.equal(patched.facts[0].sourceId, "depttrans-zone-001");
assert.equal(patched.facts[0].geometry.type, "MultiPolygon");
assert.equal(patched.facts[0].attributes.max_speed_kph, 15);
assert.equal((await manager.status(input)).consumer.upstreamStreamCount, 1);
lease.release();
await waitUntil(() => streamCounters.closed === 1, "zone_stream_closed");
await waitUntil(async () => (await manager.status(input)).consumer.runtimeState === "idle", "zone_stream_idle");
} finally {
await manager.shutdown();
await rm(stateDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
}
});
test("zone v2 consumer fails closed on projection, product, envelope and geometry contract drift", async () => {
const cases = [
{
name: "projection",
target: zoneV2Target(zoneV2Projection.slice(0, -1)),
product: zoneV2Product,
policy: zoneV2Policy,
snapshot: zoneSnapshot(),
planError: /data_product_consumer_contract_mismatch/,
},
{
name: "field-type",
target: zoneV2Target(),
product: { ...zoneV2Product, fieldContracts: { ...zoneV2Product.fieldContracts, max_speed_kph: { type: "string" } } },
policy: zoneV2Policy,
snapshot: zoneSnapshot(),
planError: /data_product_consumer_contract_mismatch/,
},
{
name: "product-version",
target: zoneV2Target(),
product: { ...zoneV2Product, version: "2.0.1" },
policy: zoneV2Policy,
snapshot: zoneSnapshot({ version: "2.0.1" }),
planError: /data_product_consumer_policy_not_found/,
},
{
name: "envelope-version",
target: zoneV2Target(),
product: zoneV2Product,
policy: zoneV2Policy,
snapshot: zoneSnapshot({ version: "2.0.1" }),
applyError: /data_product_snapshot_product_mismatch/,
},
{
name: "point-geometry",
target: zoneV2Target(),
product: zoneV2Product,
policy: zoneV2Policy,
snapshot: zoneSnapshot({ facts: [zoneFact({ geometryType: "Point" })] }),
applyError: /data_product_consumer_fact_contract_invalid/,
},
];
for (const variant of cases) {
const stateDir = await mkdtemp(join(tmpdir(), `foundry-consumer-zone-v2-${variant.name}-`));
const manager = createFoundryDataProductConsumerManager({
stateDir,
dataPlaneUrl: "http://edp.test",
resolveTarget: async () => structuredClone(variant.target),
readReaderToken: async () => "ndc_edprb_zone-reader-capability",
inspectReaderGrant: async () => ({ product: variant.product, readerGrantAction: "reuse", readerGrantGeneration: 1 }),
resolvePolicy: () => variant.policy,
sanitizeSnapshot: (value) => value,
sanitizePatch: (value) => value,
fetchImpl: async () => Response.json(variant.snapshot),
});
const input = { applicationId: variant.target.application.id, pageId: variant.target.page.id, bindingId: variant.target.binding.id };
try {
if (variant.planError) {
await assert.rejects(manager.plan(input), variant.planError);
continue;
}
const plan = await manager.plan(input);
await assert.rejects(manager.apply({ ...input, planId: plan.planId }), variant.applyError);
const failed = await manager.status(input);
assert.equal(failed.consumer.subjectCount, 0);
assert.equal(failed.consumer.runtimeState, "error");
} finally {
await manager.shutdown();
await rm(stateDir, { recursive: true, force: true });
}
}
});