From afd0b4ec460b5df0e9d94f1ec61ed8e71157e242 Mon Sep 17 00:00:00 2001 From: SIAX Agent Date: Sat, 7 Nov 2026 17:30:40 +0100 Subject: [PATCH 1/2] fix(canonical): serialisera timestamptz som RFC3339 (ISO-8601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit queryFacts använde String(Date) -> 'Sat Nov 07 2026 15:00:00 GMT+0000 (...)', vilket L0RE:s canonical-projection avvisar ('must be RFC3339 date-time'). Inför iso()-helper: Date -> toISOString(), annars String(). Gäller observed_at, valid_from, valid_to, created_at. --- sql/023_canonical_grants.sql | 2 +- src/repositories/canonical-fact-repository.ts | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sql/023_canonical_grants.sql b/sql/023_canonical_grants.sql index 8fe96b2..fb5dd0e 100644 --- a/sql/023_canonical_grants.sql +++ b/sql/023_canonical_grants.sql @@ -13,7 +13,7 @@ BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '0pt_app') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON opt_canonical_entities, opt_canonical_facts, opt_canonical_relations - TO 0pt_app; + TO "0pt_app"; END IF; END $$; diff --git a/src/repositories/canonical-fact-repository.ts b/src/repositories/canonical-fact-repository.ts index 4e22ed7..3c13a7a 100644 --- a/src/repositories/canonical-fact-repository.ts +++ b/src/repositories/canonical-fact-repository.ts @@ -30,6 +30,11 @@ export interface CanonicalFactRepository { queryFacts(query: CanonicalQuery): Promise; } +/** PostgreSQL timestamptz arrives as Date; the API contract is RFC3339/ISO-8601. */ +function iso(value: unknown): string { + return value instanceof Date ? value.toISOString() : String(value); +} + export function entityKey(e: CanonicalEntityInput): string { return `${e.entity_type}\u0000${e.identity_namespace}\u0000${e.identity_value}`; } @@ -216,11 +221,11 @@ class PostgresCanonicalFactRepository implements CanonicalFactRepository { source_record_id: r.source_record_id ?? undefined, st0re_artifact_id: r.st0re_artifact_id ?? undefined, aud0_evidence_ref: r.aud0_evidence_ref ?? undefined, run_ref: r.run_ref ?? undefined, - extractor_version: r.extractor_version, observed_at: String(r.observed_at), - valid_from: r.valid_from ? String(r.valid_from) : undefined, - valid_to: r.valid_to ? String(r.valid_to) : undefined, + extractor_version: r.extractor_version, observed_at: iso(r.observed_at), + valid_from: r.valid_from ? iso(r.valid_from) : undefined, + valid_to: r.valid_to ? iso(r.valid_to) : undefined, confidence: r.confidence === null ? undefined : Number(r.confidence), - quality_status: r.quality_status, status: r.status, created_at: String(r.created_at), + quality_status: r.quality_status, status: r.status, created_at: iso(r.created_at), })) as CanonicalFact[]; }); } -- 2.54.0 From b7627a3a31a87802c5e38852f896df92aae7a216 Mon Sep 17 00:00:00 2001 From: SIAX Agent Date: Sat, 7 Nov 2026 17:37:33 +0100 Subject: [PATCH 2/2] docs(runbook): legacy-instansaudit server5 (0pt + L0RE) och disposition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0pt legacy: 32 kanoniska facts exporterade och återinförda via API (replay duplicates=32), dump till server11, api/web stoppade med restart=no, pg kvar för forensik. L0RE-orphan refererad till PR #485. Ingen databas/volymer raderade. --- docs/runbook/legacy-instances-20261107.md | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/runbook/legacy-instances-20261107.md diff --git a/docs/runbook/legacy-instances-20261107.md b/docs/runbook/legacy-instances-20261107.md new file mode 100644 index 0000000..7358857 --- /dev/null +++ b/docs/runbook/legacy-instances-20261107.md @@ -0,0 +1,47 @@ +# Legacy instance audit — 0pt (server5) and L0RE (server5) — 2026-11-07 + +Canonical runtimes: **0pt on server6** (Coolify app `shxd6lepxuxfsjsp0ath3z1g`, +`0pt.app`) and **L0RE on server9** (`l0re.siax.io`). This document records the +older instances on server5 and their disposition. It is not production +documentation for either product. + +## 1. 0pt legacy (server5) + +| Item | Value | +|---|---| +| Containers | `0pt-api` (`0pt-api:c46ebbf`, created 2026-11-05), `0pt-web` (`:18080`) | +| Database | `0pt-pg`, user/db `opt`, 11 MB | +| Exposure | `0pt-web` only on `100.124.7.55:18080`; no DNS record, no reverse proxy | +| Consumers | none found (`/etc/siax`, `/opt/siax`, site0 and L0RE deploy configs) | +| Unique data | 32 canonical facts (30 GLEIF web observations + `body_type` YDL405 + `passmark.find_prime_numbers.v10`) | + +**Data reconciliation:** all 32 facts were exported and re-imported through the +canonical API (`POST /api/0pt/canonical/facts`) into the server6 deployment. +Replay returned `duplicates: 32`, so the import is idempotent. Production now +holds 34 facts (32 migrated + 2 verification observations) and 4 entities. + +**Backup:** `server5:/var/backups/0pt-legacy/opt_legacy_20261107.dump.zst` +(custom format, 32 KB) + offsite `server11:/home/backup/0pt/`. + +**Action taken:** `docker update --restart=no` + `docker stop` for `0pt-api` and +`0pt-web`. `0pt-pg` left running for forensics/rollback. `0pt.app` verified 200 +after the stop (served by server6). + +**Full removal (requires owner decision):** snapshot/destroy `0pt-pg` volume +after the retention window, remove the legacy compose project, revoke the old +`INTERNAL_TOKEN` if it was ever stored on server5. + +## 2. L0RE orphan (server5) + +Documented separately in `admin/l0re` PR #485 +(`docs/runbook/l0re-orphan-decommission-20261107.md`): API stopped with +`restart=no`, dump + restore proof (8 propositions/62 assertions), no unique +legitimate data (pilot facts already re-projected to production). `l0re-pg` and +`l0re-opensearch` remain for forensics. + +## 3. Single-runtime rule + +One primary runtime per logical service. A container image of a product running +outside its registered Coolify app is a finding, not a fallback. Both server5 +instances were reachable only from the host, which is why the split survived +until this audit. -- 2.54.0