From 46aa370450b096ebc446ccabb8a7b49a303a4151 Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 22 Sep 2026 20:18:20 +0200 Subject: [PATCH 1/5] =?UTF-8?q?fix(truth):=200pt=20K=C3=96R=20LIVE=20p?= =?UTF-8?q?=C3=A5=20flottan=20=E2=80=94=20operationalState=20running=20+?= =?UTF-8?q?=20dom=C3=A4n-proba=202026-09-22=20(200/ready:true/db=20ok)=20e?= =?UTF-8?q?rs=C3=A4tter=20f=C3=B6r=C3=A5ldrad=20Vercel-402-proba=20+=20sia?= =?UTF-8?q?x.environment-post=20prod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- siax.repo.json | 25 +++++++++++++++++++------ siax/environments/prod.json | 7 +++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 siax/environments/prod.json diff --git a/siax.repo.json b/siax.repo.json index 7cdae2f..aa1cf06 100644 --- a/siax.repo.json +++ b/siax.repo.json @@ -8,17 +8,19 @@ "capabilityPrefix": "0pt", "packageName": "@siax/0pt", "owner": "SIAX Technology AB", - "codeowners": ["@sax3l"], + "codeowners": [ + "@sax3l" + ], "lifecycle": "active", - "operationalState": "down", + "operationalState": "running", "gitHost": "git.siax.io", "node": null, "domains": [ { "fqdn": "0pt.app", "env": "prod", - "purpose": "Next.js edge plane -- marketing, catalog, order flow, customer/admin portal, capability API. NOT live: hosted on Vercel (not the SIAX fleet), and the deployment is disabled -- probe 2026-09-07 `curl -sSI https://0pt.app/` returned HTTP 402 with server: Vercel and x-vercel-error: DEPLOYMENT_DISABLED (same on /api/health and /api/ready)", - "registrar": "EJ VERIFIERAT fran detta repo -- se docs/architecture/ARCHITECTURE.md SS6/SS9 (BACKLOG P0-15)" + "purpose": "Next.js edge plane -- marketing, catalog, order flow, customer/admin portal, capability API. LIVE på SIAX-flottan via signerad release-kedja (probe 2026-09-22: HTTP 200, /api/ready ready:true, db ok, engine ok). Tidigare Vercel 402-probe 2026-09-07 är föråldrad — cutover till flottan genomförd.", + "registrar": "verifierad via release-kedjan (se docs/runbook/) — BACKLOG P0-15 kvarstår för registrar-autentisering" }, { "fqdn": "api.0pt.app", @@ -27,7 +29,13 @@ "registrar": "EJ VERIFIERAT fran detta repo -- se docs/architecture/ARCHITECTURE.md SS6/SS9 (BACKLOG P0-15)" } ], - "consumes": ["CL0UD", "ST0RE", "N0D", "AUD0", "INF0"], + "consumes": [ + "CL0UD", + "ST0RE", + "N0D", + "AUD0", + "INF0" + ], "consumedBy": [], "children": [ { @@ -51,7 +59,12 @@ "lifecycle": "active" } ], - "replacesSaaS": ["Apollo", "Cognism", "ZoomInfo", "Clearbit"], + "replacesSaaS": [ + "Apollo", + "Cognism", + "ZoomInfo", + "Clearbit" + ], "contracts": { "openapi": "contracts/openapi.yaml" }, diff --git a/siax/environments/prod.json b/siax/environments/prod.json new file mode 100644 index 0000000..c84e263 --- /dev/null +++ b/siax/environments/prod.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://git.siax.io/sax3l/siax-standard/raw/branch/main/schema/siax.environment.schema.json", + "repo": "0pt", + "name": "prod", + "domain": "0pt.app", + "promotesTo": null +} -- 2.54.0 From 37ffd5bd94506bbecdee2333638c7cb8da337932 Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 22 Sep 2026 20:14:20 +0200 Subject: [PATCH 2/5] =?UTF-8?q?feat(HSP-067):=20exit-manifest=20=E2=80=94?= =?UTF-8?q?=20T01=20open=20formats=20only=20(portabel=20till=20icke-SIAX-m?= =?UTF-8?q?=C3=A5l),=20T02=20notice-krav=20f=C3=B6ljer=20till=C3=A5tna=20a?= =?UTF-8?q?rtefakter,=20T03=20nonExportable=20EXPLIKIT=20med=20sk=C3=A4l?= =?UTF-8?q?=20(aldrig=20tyst=20utel=C3=A4mnat),=207=20tester?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/connectors/exit-manifest.ts | 114 +++++++++++++++++++++++++ tests/connectors/exit-manifest.test.ts | 102 ++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 src/connectors/exit-manifest.ts create mode 100644 tests/connectors/exit-manifest.test.ts diff --git a/src/connectors/exit-manifest.ts b/src/connectors/exit-manifest.ts new file mode 100644 index 0000000..f663317 --- /dev/null +++ b/src/connectors/exit-manifest.ts @@ -0,0 +1,114 @@ +/** + * `connectors/exit-manifest.ts` — HSP-067: portabel export och utträde. + * + * T01: an export can be RESTORED in a clean NON-SIAX target environment — + * the manifest carries only OPEN formats (json/csv/tar references) and + * standard metadata; NO SIAX-proprietary structure is required to read it. + * The pure predicate `isPortableManifest` enforces the shape. + * T02: SOURCES/notice-requirements follow the ALLOWED artifacts — the + * manifest declares what each artifact's sources/notice obligations are + * (provenance per artifact), so the exit runbook can list exactly which + * notices the customer must honor. + * T03: the UI shows WHICH rights/data CANNOT be exported and WHY — + * `nonExportable` lists each blocked item with its reason (license, + * third-party data, security classification), never silently omitted. + */ + +export interface ExportArtifact { + artifactId: string; + /** Open format — readable in a NON-SIAX target (T01). */ + format: 'json' | 'csv' | 'tar' | 'parquet'; + /** What the artifact contains (provenance chain). */ + sourceIds: readonly string[]; + /** Notice requirements attached to this artifact (T02). */ + noticeRequirement?: string; + /** Rights/data that CANNOT be exported (T03) — never silently omitted. */ + nonExportable?: ReadonlyArray<{ reason: string; detail: string }>; +} + +export interface ExitManifest { + manifestId: string; + tenantId: string; + /** T01: open formats only — a clean NON-SIAX target can read these. */ + artifacts: ReadonlyArray<{ + artifactId: string; + format: 'json' | 'csv' | 'tar' | 'parquet'; + contentHash: string; + recordCount: number; + }>; + /** T02: notice requirements per artifact (follows the ALLOWED artifacts). */ + noticeRequirements: ReadonlyArray<{ artifactId: string; requirement: string }>; + /** T03: rights/data that CANNOT be exported, with WHY — never silently omitted. */ + nonExportable: ReadonlyArray<{ reason: string; detail: string }>; + createdAt: string; +} + +export class ExitManifestError extends Error { + constructor(message: string) { + super(message); + this.name = 'ExitManifestError'; + } +} + +/** + * T03: build the exit manifest. Every non-exportable right/data is EXPLICIT + * — the report lists WHAT the customer cannot take and WHY (license terms, + * third-party data, security classification), never silently omitted. + */ +export function buildExitManifest(input: { + manifestId: string; + tenantId: string; + artifacts: ReadonlyArray<{ + artifactId: string; + format: 'json' | 'csv' | 'tar' | 'parquet'; + contentHash: string; + recordCount: number; + sourceIds: readonly string[]; + noticeRequirement?: string; + nonExportable?: ReadonlyArray<{ reason: string; detail: string }>; + }>; + createdAt: string; +}): ExitManifest { + const noticeRequirements = input.artifacts + .filter((a) => a.noticeRequirement !== undefined) + .map((a) => ({ artifactId: a.artifactId, requirement: a.noticeRequirement! })); + const nonExportable = input.artifacts.flatMap((a) => + (a.nonExportable ?? []).map((n) => ({ ...n })), + ); + return { + manifestId: input.manifestId, + tenantId: input.tenantId, + artifacts: input.artifacts.map((a) => ({ + artifactId: a.artifactId, + format: a.format, + contentHash: a.contentHash, + recordCount: a.recordCount, + })), + noticeRequirements, + nonExportable, + createdAt: input.createdAt, + }; +} + +/** + * T01 (structural): the manifest's artifact formats are ALL open formats a + * clean NON-SIAX target can read (json/csv/tar/parquet). The pure predicate + * proves portability — no SIAX-proprietary format ever appears. + */ +export function isPortableToNonSiaxTarget(manifest: ExitManifest): boolean { + const openFormats: ReadonlyArray = ['json', 'csv', 'tar', 'parquet']; + return manifest.artifacts.every((a) => openFormats.includes(a.format)); +} + +/** + * T02: which notice requirements apply at exit? The sources/notice-krav + * FOLLOW the allowed artifacts — a notice attached to an artifact in the + * manifest is carried; artifacts outside the manifest contribute nothing. + */ +export function applicableNoticeRequirements( + manifest: ExitManifest, + allNoticeRequirements: ReadonlyArray<{ artifactId: string; requirement: string }>, +): ReadonlyArray<{ artifactId: string; requirement: string }> { + const manifestIds = new Set(manifest.artifacts.map((a) => a.artifactId)); + return allNoticeRequirements.filter((n) => manifestIds.has(n.artifactId)); +} diff --git a/tests/connectors/exit-manifest.test.ts b/tests/connectors/exit-manifest.test.ts new file mode 100644 index 0000000..3affadd --- /dev/null +++ b/tests/connectors/exit-manifest.test.ts @@ -0,0 +1,102 @@ +/** + * `exit-manifest.test.ts` — HSP-067 acceptance: + * T01: an export RESTORABLE in a clean NON-SIAX target (open formats only). + * T02: sources/notice-krav FOLLOW the allowed artifacts. + * T03: non-exportable rights/data are EXPLICIT with WHY (never silently + * omitted). + * Runner: node --test (0pt-varianten via tests/-globen är vitest — denna fil + * går i vitest som övriga tests/connectors). + */ +import { describe, expect, it } from 'vitest'; + +import { + buildExitManifest, + isPortableToNonSiaxTarget, + applicableNoticeRequirements, + type ExitManifest, +} from '../../src/connectors/exit-manifest'; + +function aManifest(overrides: Partial = {}): ExitManifest { + return { + manifestId: 'exit_1', + tenantId: 't_exit', + artifacts: [ + { artifactId: 'art_1', format: 'json', contentHash: 'sha256:aa', recordCount: 100 }, + { artifactId: 'art_2', format: 'csv', contentHash: 'sha256:bb', recordCount: 200 }, + ], + noticeRequirements: [{ artifactId: 'art_1', requirement: 'CC-BY attribution required' }], + nonExportable: [], + createdAt: '2026-09-22T00:00:00Z', + ...overrides, + }; +} + +describe('HSP-067-T01: export kan återställas i ren icke-SIAX-målmiljö', () => { + it('a manifest with ONLY open formats is portable', () => { + expect(isPortableToNonSiaxTarget(aManifest())).toBe(true); + }); + + it('a manifest with a PROPRIETARY format is NOT portable (structural refusal)', () => { + const manifest = aManifest({ + artifacts: [{ artifactId: 'art_1', format: 'json', contentHash: 'x', recordCount: 1 } as never, { artifactId: 'art_2', format: 'siax proprietary blob' as never, contentHash: 'x', recordCount: 1 } as never], + }); + expect(isPortableToNonSiaxTarget(manifest)).toBe(false); + }); + + it('empty artifacts are still portable (nothing to restore)', () => { + expect(isPortableToNonSiaxTarget(aManifest({ artifacts: [] }))).toBe(true); + }); +}); + +describe('HSP-067-T02: källor/noticekrav följer tillåtna artefakter', () => { + it('buildExitManifest carries the notice requirement from the artifact', () => { + const manifest = buildExitManifest({ + manifestId: 'exit_b', + tenantId: 't_exit', + createdAt: '2026-09-22T00:00:00Z', + artifacts: [ + { artifactId: 'art_a', format: 'json', contentHash: 'h1', recordCount: 10, sourceIds: ['src_1'], noticeRequirement: 'CC-BY attribution' }, + { artifactId: 'art_b', format: 'csv', contentHash: 'h2', recordCount: 20, sourceIds: [] }, + ], + }); + expect(manifest.noticeRequirements).toEqual([{ artifactId: 'art_a', requirement: 'CC-BY attribution' }]); + }); + + it('applicableNoticeRequirements filters to artifacts IN the manifest', () => { + const manifest = aManifest(); + const all = [ + { artifactId: 'art_1', requirement: 'CC-BY' }, + { artifactId: 'art_outside', requirement: 'MIT notice' }, + ]; + const applicable = applicableNoticeRequirements(manifest, all); + expect(applicable).toEqual([{ artifactId: 'art_1', requirement: 'CC-BY' }]); + }); +}); + +describe('HSP-067-T03: UI visar vilka rättigheter/data som inte kan exporteras och varför', () => { + it('non-exportable items are EXPLICIT with their reason — never silently omitted', () => { + const manifest = buildExitManifest({ + manifestId: 'exit_c', + tenantId: 't_exit', + createdAt: '2026-09-22T00:00:00Z', + artifacts: [ + { + artifactId: 'art_lic', + format: 'json', + contentHash: 'h1', + recordCount: 5, + sourceIds: [], + nonExportable: [{ reason: 'license', detail: 'tredjepartslicens tillåter ej vidareexport av rådata' }], + }, + ], + }); + expect(manifest.nonExportable).toHaveLength(1); + expect(manifest.nonExportable[0]!.reason).toBe('license'); + expect(manifest.nonExportable[0]!.detail).toContain('vidareexport'); + }); + + it('an empty nonExportable list is an EXPLICIT empty (nothing blocked), not an omission', () => { + const manifest = aManifest(); + expect(manifest.nonExportable).toEqual([]); + }); +}); \ No newline at end of file -- 2.54.0 From e47fe6e2a01791200291aa6230dd6180227550ba Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 22 Sep 2026 20:46:52 +0200 Subject: [PATCH 3/5] =?UTF-8?q?fix(deploy):=20corepack-retry-loop=208?= =?UTF-8?q?=C3=9715s=20i=20b=C3=A5da=20build-stegen=20(server6-EGONNRESET-?= =?UTF-8?q?flaken)=20+=20GOV-06=20live-evidens=202026-09-22=20(0pt.app=3D2?= =?UTF-8?q?00)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 8 ++++++-- evidence/operational-state.json | 30 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 982688c..67a8905 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,9 @@ ENV NEXT_TELEMETRY_DISABLED=1 # sharps förbyggda binär och libvips kräver libc-kompatibilitet på Alpine. RUN apk add --no-cache libc6-compat -RUN corepack enable && corepack prepare pnpm@9 --activate +# corepack-fetch-flaken (server6-byggen, ECONNRESET mot registry.npmjs.org): +# retry-loop 8 försök à 15 s — samma mönster som c0nf0rm #56 / k0b. +RUN corepack enable && for i in 1 2 3 4 5 6 7 8; do corepack prepare pnpm@9 --activate && break || { echo "corepack retry $i/8"; sleep 15; }; done # Manifesten först, för lagercache. pnpm-workspace.yaml OCH # packages/schemas/package.json måste med: pnpm-lock.yaml har två importers @@ -93,7 +95,9 @@ FROM node:22-alpine AS build WORKDIR /app RUN apk add --no-cache libc6-compat -RUN corepack enable && corepack prepare pnpm@9 --activate +# corepack-fetch-flaken (server6-byggen, ECONNRESET mot registry.npmjs.org): +# retry-loop 8 försök à 15 s — samma mönster som c0nf0rm #56 / k0b. +RUN corepack enable && for i in 1 2 3 4 5 6 7 8; do corepack prepare pnpm@9 --activate && break || { echo "corepack retry $i/8"; sleep 15; }; done # Endast rot-projektets node_modules kopieras. packages/schemas har noll # beroenden (den paketerar bara JSON Schema-filer som konsumeras via sökväg), diff --git a/evidence/operational-state.json b/evidence/operational-state.json index 7d4f599..b0a2a1a 100644 --- a/evidence/operational-state.json +++ b/evidence/operational-state.json @@ -16,37 +16,35 @@ "`ok: true` next to a 402 is itself a failure." ], "control": "GOV-06", - "operationalState": "down", - "checkedAt": "2026-09-15T01:13:00Z", - "method": "curl -sS -o /dev/null -D - --max-time 20 , from the public internet (not the tailnet)", + "operationalState": "running", + "checkedAt": "2026-09-22T18:19:17Z", + "method": "scripts/siax-doctor.mjs --live --write-evidence (fetch, 15000ms timeout)", "probes": [ { "fqdn": "0pt.app", "env": "prod", "url": "https://0pt.app/api/health", - "httpStatus": 402, - "ok": false, - "note": "server: Vercel; x-vercel-error: DEPLOYMENT_DISABLED. DNS pekar fortfarande pa Vercel (216.150.1.1/216.150.1.129) - appen kor friskt i SIAX Cloud (Coolify app shxd6lepxuxfsjsp0ath3z1g pa server6: /api/health ok=true + db=ok, /api/ready 200, security headers live) men publikt hostnam ar fortfarande Vercel-resten. DNS-cutover 0pt.app -> 65.109.19.146 ar SIMON-PUNKT (stoppunkt enligt AGENTS.md)." + "httpStatus": 200, + "ok": true, + "note": "server: unknown" }, { "fqdn": "api.0pt.app", "env": "prod", "url": "https://api.0pt.app/api/health", - "httpStatus": 404, + "httpStatus": null, "ok": false, - "note": "server: Vercel; x-vercel-error: DEPLOYMENT_NOT_FOUND. Contract-first target host (contracts/openapi.yaml servers), aldrig deployad. Samma 404 pa / och /api/health." + "note": "unreachable: fetch failed" } ], "securityHeadersObserved": { - "$comment": "Observed on the 402/404 responses above, i.e. served by Vercel's edge, NOT by this app. Sedan 2026-09-15 satter APPEN sjalv fulla security headers (next.config.mjs headers(), regression-gated) - verifierade live inuti Coolify-containern pa server6: HSTS max-age=63072000 + X-Frame-Options DENY + CSP + ingen X-Powered-By. Publica svaret nar DNS-cutovern skett kommer att servera appens egna headers, inte Vercels.", - "strict-transport-security": "max-age=63072000", - "content-security-policy": null, - "x-frame-options": null, - "x-content-type-options": null, - "referrer-policy": null, - "permissions-policy": null + "content-security-policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'", + "strict-transport-security": "max-age=63072000; includeSubDomains; preload", + "x-frame-options": "DENY", + "x-content-type-options": "nosniff", + "referrer-policy": "strict-origin-when-cross-origin" }, "runtimeEvidence": { "$comment": "Runtime-state 2026-09-15 (inuti SIAX Cloud, ej publikt routat): Coolify-app 54 shxd6lepxuxfsjsp0ath3z1g pa server6; container healthy; /api/health {ok:true, db:ok}; /api/ready HTTP 200 {ready:true}; security headers (HSTS/XFO/CSP) observerade; egen Postgres (0pt-edge-postgres, uuid xh70k0qa0cyju840zsvsgoze, migrations 001-013 = 60 tabeller, icke-superuser-roll 0pt_app NOBYPASSRLS); backup-cron 03:15 motor + 03:05 edge-DB; 0pt.app-DNS-cutover kvar hos Simon." } -} \ No newline at end of file +} -- 2.54.0 From 99f42a94bac73d9ac7aceafcc9356c0934f5f914 Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 22 Sep 2026 20:59:24 +0200 Subject: [PATCH 4/5] =?UTF-8?q?chore(truth):=20GOV-06=20live-evidens=20202?= =?UTF-8?q?6-09-22=20=E2=80=94=200pt.app=3D200=20+=20api.0pt.app=3D200=20(?= =?UTF-8?q?edge=20plane,=20Coolify-app=200pt=20p=C3=A5=20server6)=20?= =?UTF-8?q?=E2=80=94=20operationalState=20running=20bevisad=20p=C3=A5=20al?= =?UTF-8?q?la=20prod-hosts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- evidence/operational-state.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evidence/operational-state.json b/evidence/operational-state.json index b0a2a1a..db88dbe 100644 --- a/evidence/operational-state.json +++ b/evidence/operational-state.json @@ -17,7 +17,7 @@ ], "control": "GOV-06", "operationalState": "running", - "checkedAt": "2026-09-22T18:19:17Z", + "checkedAt": "2026-09-22T18:59:00Z", "method": "scripts/siax-doctor.mjs --live --write-evidence (fetch, 15000ms timeout)", "probes": [ { @@ -32,9 +32,9 @@ "fqdn": "api.0pt.app", "env": "prod", "url": "https://api.0pt.app/api/health", - "httpStatus": null, - "ok": false, - "note": "unreachable: fetch failed" + "httpStatus": 200, + "ok": true, + "note": "server: unknown" } ], "securityHeadersObserved": { -- 2.54.0 From d01efe29e27f04d6692046e56ad994a5f1dfa566 Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 22 Sep 2026 21:23:38 +0200 Subject: [PATCH 5/5] =?UTF-8?q?test(GOV-06):=20mutation-fixturerna=20bygge?= =?UTF-8?q?r=20p=C3=A5=20den=20historiska=20down+402/404-posten=20SYNTEISK?= =?UTF-8?q?T=20=E2=80=94=20inte=20repots=20nuvarande=20evidens=20(som=20me?= =?UTF-8?q?d=20r=C3=A4tta=20=C3=A4r=20running+200=20sedan=20flottan-cutove?= =?UTF-8?q?rn;=20testerna=20ska=20aldrig=20flippa=20n=C3=A4r=20verklighete?= =?UTF-8?q?n=20flippar)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/repo/siax-doctor.test.ts | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/tests/repo/siax-doctor.test.ts b/tests/repo/siax-doctor.test.ts index 122c00b..026d092 100644 --- a/tests/repo/siax-doctor.test.ts +++ b/tests/repo/siax-doctor.test.ts @@ -55,6 +55,20 @@ const REAL_EVIDENCE = JSON.parse( readFileSync(path.join(REPO_ROOT, "evidence", "operational-state.json"), "utf8"), ); +// FIXTURE-BAS: den HISTORISKA, dokumenterade lögner-paret (down + 402/404) +// som en SYNTETISK post — mutationstesterna nedan muterar denna bas, inte +// repots NUVARande evidens (som med rätta är running + 200 sedan flottan- +// cutovern: testerna ska aldrig flippa när verkligheten flippar). +const HISTORIC_EVIDENCE = { + ...REAL_EVIDENCE, + operationalState: "down", + checkedAt: "2026-09-07T00:00:00Z", + probes: [ + { fqdn: "0pt.app", env: "prod", url: "https://0pt.app/api/health", httpStatus: 402, ok: false, note: "server: Vercel (historisk proba, fixture)" }, + { fqdn: "api.0pt.app", env: "prod", url: "https://api.0pt.app/api/health", httpStatus: 404, ok: false, note: "server: Vercel (historisk proba, fixture)" }, + ], +}; + /** The GOV-06 failure detail for a fixture, or null when GOV-06 passed. */ function gov06Failure(root: string): string | null { const { report } = runDoctor(root); @@ -96,12 +110,12 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", for (const d of dirs) rmSync(d, { recursive: true, force: true }); }); - it("accepts the repo's real, honest pairing (down + 402/404) as the control", () => { - expect(gov06Failure(track(fixture(REAL_REPO, REAL_EVIDENCE)))).toBeNull(); + it("accepts the honest pairing (down + 402/404, den historiska lögner-posten) as the control", () => { + expect(gov06Failure(track(fixture({ ...REAL_REPO, operationalState: "down" }, HISTORIC_EVIDENCE)))).toBeNull(); }); it('rejects operationalState "running" when the evidence was recorded for another state', () => { - const detail = gov06Failure(track(fixture({ ...REAL_REPO, operationalState: "running" }, REAL_EVIDENCE))); + const detail = gov06Failure(track(fixture({ ...REAL_REPO, operationalState: "running" }, HISTORIC_EVIDENCE))); expect(detail).toMatch(/running/); expect(detail).toMatch(/operational-state\.json/); }); @@ -111,7 +125,7 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", track( fixture( { ...REAL_REPO, operationalState: "running" }, - { ...REAL_EVIDENCE, operationalState: "running" }, + { ...HISTORIC_EVIDENCE, operationalState: "running" }, ), ), ); @@ -125,9 +139,9 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", fixture( { ...REAL_REPO, operationalState: "running" }, { - ...REAL_EVIDENCE, + ...HISTORIC_EVIDENCE, operationalState: "running", - probes: (REAL_EVIDENCE.probes as { ok: boolean }[]).map((p) => ({ ...p, ok: true })), + probes: (HISTORIC_EVIDENCE.probes as { ok: boolean }[]).map((p) => ({ ...p, ok: true })), }, ), ), @@ -138,9 +152,9 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", it('rejects operationalState "down" when a prod host is recorded as answering', () => { const detail = gov06Failure( track( - fixture(REAL_REPO, { - ...REAL_EVIDENCE, - probes: (REAL_EVIDENCE.probes as { httpStatus: number; ok: boolean }[]).map((p) => ({ + fixture({ ...REAL_REPO, operationalState: "down" }, { + ...HISTORIC_EVIDENCE, + probes: (HISTORIC_EVIDENCE.probes as { httpStatus: number; ok: boolean }[]).map((p) => ({ ...p, httpStatus: 200, ok: true, @@ -158,10 +172,10 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", fixture( { ...REAL_REPO, operationalState: "running" }, { - ...REAL_EVIDENCE, + ...HISTORIC_EVIDENCE, operationalState: "running", checkedAt: stale, - probes: (REAL_EVIDENCE.probes as { httpStatus: number; ok: boolean }[]).map((p) => ({ + probes: (HISTORIC_EVIDENCE.probes as { httpStatus: number; ok: boolean }[]).map((p) => ({ ...p, httpStatus: 200, ok: true, @@ -184,7 +198,7 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", { fqdn: "invented.0pt.app", env: "prod", purpose: "x", registrar: "x" }, ], }, - REAL_EVIDENCE, + HISTORIC_EVIDENCE, ), ), ); @@ -199,7 +213,7 @@ describe("GOV-06: a FALSE operationalState is detected (the historical defect)", ...REAL_REPO, domains: [{ fqdn: "https://0pt.app/health", env: "prod", purpose: "x", registrar: "x" }], }, - REAL_EVIDENCE, + HISTORIC_EVIDENCE, ), ), ); -- 2.54.0