/**
* conflict-policy — app categories (compartments) for publish conflicts.
*
* Publish conflicts are only meaningful BETWEEN apps that are supposed to
* co-version. Apps in different categories are expected to diverge — e.g.
* `experimental` apps deliberately carrying a new major during a separate
* test, or `external` apps (keet-daemon's private deps, hyper-tps the
* leading-edge canary) that we don't co-version with at all. Holding a
* version on purpose is a pin, not a conflict.
*
* Category resolution for an app, in order:
* 1. the app's own lockfile.category (self-declared via pgh.category)
* 2. the override map in ~/.config/pgh/conflict-policy.json
* 3. the policy default (default 'production')
*
* conflict-policy.json shape:
* {
* "default": "production",
* "overrides": { "keet-daemon": "external", "hyper-tps": "external",
* "pgh-experimental": "experimental", ... }
* }
*
* detectConflicts compares only rivals in the SAME category as the
* publishing app. With no policy file present, the override map is empty
* and every app falls to the default category, so conflict behavior is
* unchanged (everything compared) — categories are purely additive.
*/
import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'
export const DEFAULT_CATEGORY = 'production'
export const POLICY_PATH = path.join(homedir(), '.config', 'pgh', 'conflict-policy.json')
export function loadPolicy (policyPath = POLICY_PATH) {
let raw = {}
try { raw = JSON.parse(fs.readFileSync(policyPath, 'utf8')) } catch { raw = {} }
const def = typeof raw.default === 'string' ? raw.default : DEFAULT_CATEGORY
const overrides = (raw.overrides && typeof raw.overrides === 'object') ? raw.overrides : {}
return {
default: def,
overrides,
// Resolve an app's category: self-declared lockfile.category wins, then
// the override map, then the default.
categoryOf (appName, lockfile) {
if (lockfile && typeof lockfile.category === 'string') return lockfile.category
if (Object.prototype.hasOwnProperty.call(overrides, appName)) return overrides[appName]
return def
}
}
}