---
name: save-before-publish-checks
description: >
  Load when wiring pre-save or pre-publish checks with safeMigrateBody,
  migrateBody, BodyMigrationError, BodyMigrationIssue, lintBody,
  BodyLintReport, lint severity policy, persisted JSON handling, future
  version rejection, and excluding DB persistence or request handling.
type: lifecycle
library: "@ryhrm-gz/xincodo-lib"
library_version: "0.1.0"
requires:
  - building-and-parsing-body
  - validating-body
sources:
  - "ryhrm-gz/xincodo-lib:README.md"
  - "ryhrm-gz/xincodo-lib:src/migration.ts"
  - "ryhrm-gz/xincodo-lib:src/lint.ts"
  - "ryhrm-gz/xincodo-lib:tests/migration.test.ts"
  - "ryhrm-gz/xincodo-lib:tests/lint.test.ts"
---

# Save Before Publish Checks

This skill builds on `building-and-parsing-body` and `validating-body`.
Use it to check `Body` data at save or publish boundaries. Do not design
database persistence, request handlers, or upload flows in this skill.

## Setup

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

export function checkBodyBeforePublish(value: unknown) {
  const migrated = safeMigrateBody(value);

  if (!migrated.success) {
    return { ok: false as const, stage: "migration", issue: migrated.issue };
  }

  const report = lintBody(migrated.output);

  if (!report.valid) {
    return { ok: false as const, stage: "lint", report };
  }

  return { ok: true as const, body: migrated.output, warnings: report.warnings };
}
```

Default blocking policy: migration failures and lint errors block. Lint
warnings are surfaced for application policy.

## Core Patterns

### Check persisted JSON before save

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const migrated = safeMigrateBody(savedBodyJson);
if (!migrated.success) {
  throw new Error(migrated.issue.message);
}

const report = lintBody(migrated.output);
if (!report.valid) {
  throw new Error(report.errors.map((issue) => issue.message).join("\n"));
}
```

Treat persisted values as unknown input. Do not cast them directly to `Body`.

### Return user-facing check results

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

export function checkBody(value: unknown) {
  const migrated = safeMigrateBody(value);

  if (!migrated.success) {
    return {
      canPublish: false,
      errors: [migrated.issue.message],
      warnings: [] as string[],
    };
  }

  const report = lintBody(migrated.output);

  return {
    canPublish: report.valid,
    errors: report.errors.map((issue) => issue.message),
    warnings: report.warnings.map((issue) => issue.message),
  };
}
```

This keeps severity policy explicit without mixing in transport or storage.

### Use throwing migration only inside controlled code

```ts
import { BodyMigrationError, migrateBody } from "@ryhrm-gz/xincodo-lib";

try {
  const body = migrateBody(rawBody);
  console.log(body.version);
} catch (error) {
  if (error instanceof BodyMigrationError) {
    console.error(error.issue.message);
  }
}
```

For UI-facing save or publish checks, prefer `safeMigrateBody`.

## Common Mistakes

### HIGH Using throwing migration at app boundary

Wrong:

```ts
import { lintBody, migrateBody } from "@ryhrm-gz/xincodo-lib";

const body = migrateBody(rawBody);
const report = lintBody(body);
```

Correct:

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const migrated = safeMigrateBody(rawBody);
if (!migrated.success) {
  return { ok: false as const, issue: migrated.issue };
}

const report = lintBody(migrated.output);
```

`migrateBody` throws `BodyMigrationError`; `safeMigrateBody` returns a result
that is better for user-facing save and publish checks.

Source: `src/migration.ts`; `tests/migration.test.ts`

### CRITICAL Trusting persisted JSON directly

Wrong:

```ts
import { lintBody, type Body } from "@ryhrm-gz/xincodo-lib";

const body = savedArticle.body as Body;
const report = lintBody(body);
```

Correct:

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const migrated = safeMigrateBody(savedArticle.body);
if (!migrated.success) {
  return { ok: false as const, issue: migrated.issue };
}

const report = lintBody(migrated.output);
```

Saved Body JSON should be treated as unknown input and passed through
`safeMigrateBody` before linting or rendering.

Source: maintainer interview

### HIGH Treating future versions as migratable

Wrong:

```ts
import { BODY_VERSION, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const result = safeMigrateBody({ version: BODY_VERSION + 1, content: [] });
if (!result.success) {
  await saveRawBody(rawBody);
}
```

Correct:

```ts
import { safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const result = safeMigrateBody(rawBody);
if (!result.success) {
  throw new Error(result.issue.message);
}
```

Versions greater than `BODY_VERSION` are unsupported because there is no
downgrade path.

Source: `src/migration.ts`; `tests/migration.test.ts`

### CRITICAL Mixing storage concerns into skill scope

Wrong:

```ts
await xincodo.saveArticle({
  body,
  databaseUrl,
});
```

Correct:

```ts
import { lintBody, safeMigrateBody } from "@ryhrm-gz/xincodo-lib";

const migrated = safeMigrateBody(rawBody);
if (!migrated.success) {
  throw new Error(migrated.issue.message);
}

const report = lintBody(migrated.output);
```

The library can validate Body data before persistence, but DB schema,
request handling, and upload flows are outside its scope.

Source: maintainer interview

## References

- [Migration issues and lint severities](references/migration-issues-and-lint-severities.md)

See also: `building-and-parsing-body/SKILL.md` — persisted JSON should go
through `safeMigrateBody` before lint and publish checks can run.
