import { sha256Base64 } from '../signing/hashing.js';
export const LIFECYCLE_TERMINAL_STATES = ['ready', 'failed', 'cancelled'];
export const LIFECYCLE_NON_TERMINAL_STATES = ['pending', 'provisioning', 'migrating', 'retiring'];
export const LIFECYCLE_STATES = [...LIFECYCLE_NON_TERMINAL_STATES, ...LIFECYCLE_TERMINAL_STATES];
export const LIFECYCLE_MIGRATION_STATES = ['pending', 'migrated', 'failed'];
// The environment axis (edit/preview/deployed) lives on the binding, so a
// single `ensure_database` covers what used to be split into
// ensure_dev_database / ensure_prod_database. `migrate_schema` and
// `retire_database` were always environment-agnostic.
export const LIFECYCLE_OPERATIONS = ['ensure_database', 'migrate_schema', 'retire_database'];
// Mirrors the platform's view modes (proto api.v1.ViewMode: VIEW_MODE_EDIT /
// VIEW_MODE_PREVIEW / VIEW_MODE_DEPLOYED). Working-state migrations and the
// shared workspace database serve edit/preview; deploy-commit migrations and
// the deploy gate concern deployed.
export const LIFECYCLE_ENVIRONMENTS = ['edit', 'preview', 'deployed'];
export const DATABASE_ENGINES = ['postgres', 'snowflake', 'snowflake_postgres', 'lakebase'];
export const DATABASE_LIFECYCLE_MANAGED_BY = 'database_lifecycle';
// Agent capability tag keys. A lifecycle worker publishes these in the
// `tags` map of its agent registration (merged into — never replacing — the
// tag map the agent already publishes; the existing `profile` tag carries
// datatag coverage, same meaning it has for execution routing. Lifecycle
// environment/profile coverage is published as pairs so edit-only staging
// profiles do not imply deployed staging support. The server matches pending
// lifecycle requests against these at poll/claim time and gates task creation
// on "some active org agent supports this".
export const DATABASE_LIFECYCLE_TAG_OPERATIONS = 'databaseLifecycle:operations';
export const DATABASE_LIFECYCLE_TAG_ENGINES = 'databaseLifecycle:engines';
export const DATABASE_LIFECYCLE_TAG_ENVIRONMENT_PROFILES = 'databaseLifecycle:environmentProfiles';
export const CREDENTIAL_RESOLVERS = ['opa_local', 'aws_secrets_manager', 'gcp_secret_manager', 'vault', 'kubernetes_secret'];
export function isReadyDatabaseBinding(binding) {
    return binding.lifecycleState === 'ready';
}
// Physical database instances back the M2 shared-RDS allocation pattern: dev-DB
// provisioning issues `CREATE DATABASE`/`CREATE ROLE` against a pre-existing
// physical database instance instead of spinning up a fresh RDS per binding.
// The control plane is the dumb org-scoped state store (registry + atomic
// capacity counter); ALL selection and provisioning logic lives in the worker.
export const PHYSICAL_DATABASE_INSTANCE_STATUSES = ['active', 'draining', 'retired'];
export function computeRequirementKey(requirement) {
    return `${slugify(requirement.logicalName)}~${encodeURIComponent(requirement.logicalName)}:${requirement.engine}`;
}
export function computeBindingKey(input) {
    return [input.organizationId, ...bindingKeySegments(input)].join(':');
}
export function computeLegacyBindingKeyWithoutOrganization(input) {
    return bindingKeySegments(input).join(':');
}
function bindingKeySegments(input) {
    return [input.applicationId, input.environment, `${slugify(input.profile)}~${encodeURIComponent(input.profile)}`, input.requirementKey];
}
export async function computeDesiredSpecHash(requirement) {
    return await sha256Base64(JSON.stringify(canonicalize(requirement)));
}
// resource_key identifies the physical resource a binding maps to in
// customer infrastructure, and is the unit of locking inside the lifecycle
// worker. Distinct from binding_key (product identity in the control plane)
// because the worker derives infrastructure identity from it — shape
// mirrors planning doc §15.
export function computeResourceKey(input) {
    return [
        input.organizationId,
        input.applicationId,
        input.requirementKey,
        input.environment,
        `${slugify(input.profile)}~${encodeURIComponent(input.profile)}`,
        input.actorScope ?? 'default'
    ].join('/');
}
export function isTerminalLifecycleState(state) {
    return LIFECYCLE_TERMINAL_STATES.includes(state);
}
export function redactLifecycleSecrets(value) {
    return redact(value);
}
function slugify(value) {
    return value
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '');
}
function canonicalize(value) {
    if (Array.isArray(value)) {
        return value.map(canonicalize).sort((left, right) => codepointCompare(JSON.stringify(left), JSON.stringify(right)));
    }
    if (value && typeof value === 'object') {
        return Object.fromEntries(Object.entries(value)
            .filter(([, entryValue]) => entryValue !== undefined)
            .sort(([left], [right]) => codepointCompare(left, right))
            .map(([key, entryValue]) => [key, canonicalize(entryValue)]));
    }
    return value;
}
function codepointCompare(left, right) {
    if (left < right) {
        return -1;
    }
    if (left > right) {
        return 1;
    }
    return 0;
}
function redact(value, preserveCredentialRefs = false) {
    if (Array.isArray(value)) {
        return value.map((entry) => redact(entry, preserveCredentialRefs));
    }
    if (value && typeof value === 'object') {
        return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
            if (isCredentialRefKey(key)) {
                return [key, redact(entryValue, true)];
            }
            if (!preserveCredentialRefs && SECRET_KEY_PATTERN.test(key)) {
                return [key, '[REDACTED]'];
            }
            return [key, redact(entryValue, preserveCredentialRefs)];
        }));
    }
    return value;
}
// Shared secret-key tripwire: keys whose name strongly suggests a raw secret
// value. Used by `redactLifecycleSecrets` to scrub logs/error reporting and by
// the server-side `assertNoRawCredentialMaterial` guard to refuse callbacks
// that violate the typed-CredentialRef contract. NOT a security boundary --
// callers must still send `Record<string, string | number | boolean>` for
// `connectionMetadata` and `CredentialRef` for `credentialRefs`; this catches
// obvious misuse only.
export const SECRET_KEY_PATTERN = /password|secret|token|private[_-]?key|dsn/i;
// Schemes whose URI form embeds credentials before `@`. Used as a value-side
// tripwire alongside `SECRET_KEY_PATTERN` -- callers should never send
// connection strings; the typed `connectionMetadata` shape is the contract.
export const CREDENTIAL_BEARING_DSN_SCHEMES = [
    'postgres',
    'postgresql',
    'mysql',
    'mariadb',
    'mongodb',
    'mongodb+srv',
    'redis',
    'rediss',
    'amqp',
    'amqps',
    'kafka'
];
export function isSecretKey(key) {
    return SECRET_KEY_PATTERN.test(key);
}
export function containsCredentialMaterial(value) {
    if (DSN_WITH_INLINE_AUTH.test(value)) {
        return true;
    }
    if (CREDENTIAL_KV_PATTERN.test(value)) {
        return true;
    }
    if (JSON_ENCODED_SECRET_PATTERN.test(value)) {
        return true;
    }
    return false;
}
const DSN_SCHEME_GROUP = CREDENTIAL_BEARING_DSN_SCHEMES.join('|').replace(/\+/g, '\\+');
const DSN_WITH_INLINE_AUTH = new RegExp(`(?:${DSN_SCHEME_GROUP}):\\/\\/[^/@\\s:]+:[^/@\\s]+@`, 'i');
const CREDENTIAL_KV_PATTERN = /(?:password|pwd|token|secret|private[_-]?key|api[_-]?key)\s*=\s*[^;&\s"]+/i;
const JSON_ENCODED_SECRET_PATTERN = /["']\s*(?:password|secret|token|private[_-]?key|dsn)\s*["']\s*:/i;
function isCredentialRefKey(key) {
    return /credential[_-]?ref/i.test(key);
}
//# sourceMappingURL=index.js.map