feat(0pt): canonical-fact machine route + renumber migration to 022 #484

Merged
admin merged 1 commits from fix/0pt-canonical-internal-token-v2 into main 2026-11-05 19:16:13 +00:00
7 changed files with 15 additions and 5 deletions
+4 -1
View File
@@ -4,7 +4,7 @@
* Customer calls use verified Zitadel bearer identity and receive immutable
* x-0pt-verified-* headers. Explicit machine routes may instead use the
* shared X-Internal-Token convention; mutation handlers verify it again for
* defence in depth.
* defence in depth. The canonical-fact write is one such explicit machine route.
*/
import { NextResponse, type NextRequest } from 'next/server';
import { assertAuthConfigSafe, resolveAuthContext } from '@/src/auth/context';
@@ -26,6 +26,9 @@ const EXEMPT_PATTERNS: RegExp[] = [
/** Routes that may be reached with a service token instead of end-user bearer auth. */
const INTERNAL_SERVICE_ROUTES: RegExp[] = [
/^\/api\/0pt\/quality\/bridge-run$/,
// Acquisition engine -> 0pt canonical observation write (machine-to-machine,
// same X-Internal-Token class as the quality bridge; not a blanket bypass).
/^\/api\/0pt\/canonical\/facts$/,
/^\/api\/0pt\/internal\/evidence-outbox\/dispatch$/,
/^\/api\/0pt\/internal\/er\/candidates$/,
/^\/api\/0pt\/search\/definitions$/,
@@ -1,7 +1,7 @@
/**
* src/repositories/canonical-fact-repository.ts
* =============================================
* Storage seam for 0pt canonical entities/facts/relations (sql/020).
* Storage seam for 0pt canonical entities/facts/relations (sql/022).
* Same split as source-repository.ts: one interface, an in-memory
* implementation (dev/test) and a Postgres implementation (production),
* chosen once via resolveStoreMode.
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* src/services/canonical-fact-service.ts
* ======================================
* 0pt's canonical structured-observation write/read surface (sql/020).
* 0pt's canonical structured-observation write/read surface (sql/022).
*
* Ownership boundary: 0pt stores normalized OBSERVED facts (what a source
* said, when, from which artifact). L0RE owns epistemic propositions and
+1 -1
View File
@@ -264,7 +264,7 @@ export const activateSourceSchema = z.object({
actor_id: z.string().min(1).optional(),
});
/** POST /api/0pt/canonical/facts — canonical structured observation write (sql/020). */
/** POST /api/0pt/canonical/facts — canonical structured observation write (sql/022). */
const canonicalEntityInputSchema = z.object({
entity_type: z.string().min(1),
identity_namespace: z.string().min(1),
@@ -45,6 +45,13 @@ describe('middleware.ts: internal-token path for /api/0pt/quality/bridge-run', (
expect(res.status).toBe(200);
});
it('a valid X-Internal-Token lets the acquisition engine write canonical facts', async () => {
const res = await middleware(
req('/api/0pt/canonical/facts', { 'x-internal-token': 'engine-shared-secret' }),
);
expect(res.status).toBe(200);
});
it('an invalid X-Internal-Token still falls through to the ordinary Bearer requirement (401, not a bypass)', async () => {
const res = await middleware(req('/api/0pt/quality/bridge-run', { 'x-internal-token': 'wrong-secret' }));
expect(res.status).toBe(401);
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Canonical structured observations (sql/020): idempotent write, conflict
* Canonical structured observations (sql/022): idempotent write, conflict
* preservation, tenant isolation and relation identity resolution.
*/
import { describe, it, expect } from "vitest";