{"version":3,"sources":["../src/store/claims/backing.ts","../src/store/claims/edge-claims.ts"],"names":["claimsMembers","edgeCardinalityAxis","encodeTupleKey","checkUniqueEdge","checkCardinality","CardinalityError","ConfigurationError","isMissingTableError","outcome","compareClaimTargets"],"mappings":"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","file":"chunk-G7OKJDMG.cjs","sourcesContent":["/**\n * What backs each constraint fence class.\n *\n * A constrained write is fenced by one of two things: a per-graph lock that\n * serializes the probe with the write it guards, or a CLAIM row whose primary\n * key refuses a second claimant outright. The second is strictly stronger — it\n * holds without a lock, which is what an import needs — and this module records\n * which class has reached it, so \"is this fence a lock or a key?\" is answered\n * from one table rather than inferred from whichever module a reader lands in.\n *\n * It also owns the runtime half of the same question — {@link claimSupport},\n * the one reader of \"can THIS object hold a claim?\" — so the declaration and\n * the surface are reconciled in one place rather than at each claim site.\n * `claimSupport` is now a thin binder over the `claims` capability bundle\n * (`backend/capabilities`): the bidirectional declaration/surface cross-check\n * lives once in `resolve.ts`'s `assertClaimsBidirectionalAgreement`, reached\n * through the verdict every caller threads in, and this module owns only the\n * per-write port bind (`claimsMembers`) and this decision's shape.\n */\nimport {\n  type BundleBinding,\n  claimsMembers,\n} from \"../../backend/capabilities/bind\";\nimport type { CLAIMS } from \"../../backend/capabilities/bundle-registry\";\nimport { type BundleVerdictOf } from \"../../backend/capabilities/resolve\";\nimport { type GraphBackend } from \"../../backend/types\";\n\n/**\n * The runtime extent of the fence classes. {@link ConstraintFenceReason} is\n * DERIVED from it rather than declared beside it: a union type has no runtime\n * extent, so a totality test over a map keyed by the union could only iterate\n * that map's own keys and would be trivially true.\n */\nexport const CONSTRAINT_FENCE_REASONS = [\n  \"edgeCardinality\",\n  \"edgeMatchKeyConvergence\",\n  \"nodeDisjointness\",\n  \"nodeUniquenessClaim\",\n  \"nodeUniquenessScope\",\n] as const;\n\n/**\n * WHICH declared constraint makes a write constrained.\n *\n * The classification names the reason rather than answering yes/no, because\n * the reason is load-bearing twice over: it is what the fence is taken FOR, and\n * — on a backend that cannot hold the fence — it is what the refusal has to\n * tell the caller. \"This backend cannot fence constrained writes\" is unusable\n * advice; \"your `cardinality: 'one'` edge cannot be enforced here\" is\n * actionable, and only the classifier knows which it was.\n */\nexport type ConstraintFenceReason = (typeof CONSTRAINT_FENCE_REASONS)[number];\n\n/** The relation whose key fences a class, or `lockOnly` when nothing does yet. */\ntype ConstraintFenceBacking = \"uniques\" | \"edgeClaims\" | \"lockOnly\";\n\n/**\n * Which claim relation backs each fence class. The `satisfies` forces totality:\n * a new reason cannot be added without stating what backs it.\n *\n * `nodeUniquenessScope` is `\"uniques\"` because a shared-scope claim is now\n * written at the scope's axis, so two writers of two kinds in one hierarchy\n * contend for one row. `nodeUniquenessClaim` is the same relation seen from the\n * other side: the claim row is the fence, and the class exists because a write\n * that must issue it BEFORE the row it gates needs a transaction to undo the\n * pair together — which is what its refusal names. `nodeDisjointness` is the\n * same relation again, at a third kind of axis: a claim keyed on the declared\n * PAIR and on the node's id, which is the axis disjointness actually declares\n * and the one the nodes primary key `(graph_id, kind, id)` cannot fence.\n * `edgeCardinality` is the other relation: a claim keyed on\n * `(<cardinality>:<edgeKind>, endpoint identity)`, which is the axis the\n * declaration actually spans and the one the edges primary key `(graph_id, id)`\n * cannot fence. The classes still marked `lockOnly` are fenced by the per-graph\n * write lock alone, which is why they are also the classes a non-transactional\n * backend refuses.\n */\nexport const CONSTRAINT_FENCE_BACKING = {\n  edgeCardinality: \"edgeClaims\",\n  edgeMatchKeyConvergence: \"lockOnly\",\n  nodeDisjointness: \"uniques\",\n  nodeUniquenessClaim: \"uniques\",\n  nodeUniquenessScope: \"uniques\",\n} as const satisfies Record<ConstraintFenceReason, ConstraintFenceBacking>;\n\n/** The decision {@link claimSupport} returns — never a flag a caller re-derives. */\nexport type ClaimSupport =\n  /** Capability declared AND every claim member present: the members, bound off the port. */\n  | Readonly<{\n      supported: true;\n      claims: BundleBinding<(typeof CLAIMS)[\"core\"][number]>;\n    }>\n  /** Capability absent or false AND no claim member present: the declared gap. */\n  | Readonly<{ supported: false }>;\n\n/**\n * THE one reader of \"can this object hold a claim?\", asked about the object the\n * claim is about to be written to.\n *\n * Takes the bundle's VERDICT — resolved once, against `GraphBackend`, by the\n * caller's `ClaimsVerdictThunk` (ruling B1/B7) — rather than resolving it\n * itself: the declaration/surface cross-check\n * (`resolve.ts`'s `assertClaimsBidirectionalAgreement`) runs exactly once,\n * inside that resolution, and this function never re-derives it. What this\n * function still owns is the PER-WRITE bind: `claimsMembers` (`bind.ts`) binds\n * the verdict's named members off `target` — the object the write actually\n * executes on, which may be a `TransactionBackend` the verdict was never\n * resolved against — and throws the bundle's port-surface code\n * (`CONSTRAINT_CLAIM_SURFACE_MISMATCH`) if `target` disagrees with what the\n * verdict says is present.\n *\n * @throws ConfigurationError (`CONSTRAINT_CLAIM_SURFACE_MISMATCH`) when\n *   `target` is missing a member the verdict says is present.\n */\nexport function claimSupport(\n  target: Readonly<\n    Partial<Pick<GraphBackend, (typeof CLAIMS)[\"core\"][number]>>\n  >,\n  verdict: BundleVerdictOf<typeof CLAIMS>,\n): ClaimSupport {\n  if (!verdict.supported) return { supported: false };\n  return { supported: true, claims: claimsMembers(target, verdict) };\n}\n","/**\n * Edge claims — what an edge write reserves on a declared cardinality axis.\n *\n * A declared cardinality is a predicate over `(kind, from)` or\n * `(kind, from, to)`, and the edges relation's only uniqueness is its\n * `(graph_id, id)` primary key, so nothing in the schema re-decides at write\n * time what the probe decided at read time. This module is the reservation that\n * does: one row per `(graph_id, axis, key)` in `typegraph_edge_claims`, whose\n * primary key refuses a second concurrent claimant.\n *\n * The claim needs no release path. A claim whose holder is no longer live (or,\n * for `oneActive`, no longer active) fails the liveness predicate the takeover\n * statement carries and is taken over in place, so the fence never depends on\n * any delete path having run. `purgeEdgeClaims` exists to bound table growth,\n * not to make the fence correct.\n */\nimport type { CLAIMS } from \"../../backend/capabilities/bundle-registry\";\nimport { type BundleVerdictOf } from \"../../backend/capabilities/resolve\";\nimport {\n  type ClaimEdgeCardinalityParams,\n  type GraphBackend,\n  type InsertEdgeParams,\n} from \"../../backend/types\";\nimport { checkCardinality, checkUniqueEdge } from \"../../constraints\";\nimport { type Cardinality } from \"../../core/types\";\nimport { CardinalityError, ConfigurationError } from \"../../errors\";\nimport { isMissingTableError } from \"../../utils/sql-errors\";\nimport { encodeTupleKey } from \"../../utils/tuple-key\";\nimport {\n  type ClaimTarget,\n  compareClaimTargets,\n  edgeCardinalityAxis,\n} from \"./axis\";\nimport { claimSupport } from \"./backing\";\n\n/** A cardinality that declares something — `many` declares nothing. */\nexport type ConstrainedCardinality = Exclude<Cardinality, \"many\">;\n\n/**\n * What one declared cardinality means to every layer that has to agree about\n * it: which endpoints its axis key covers, whether an edge born already ended\n * claims at all, and what a holder must still BE for its claim to stand.\n */\nexport type EdgeCardinalitySpec = Readonly<{\n  /** Which endpoints the axis key covers. */\n  keyShape: \"from\" | \"fromAndTo\";\n  /** Whether a new edge that is born ENDED makes a claim at all. */\n  claimsWhenBornEnded: boolean;\n  /** What a holder must still be for its claim to stand. */\n  holderLiveness: \"live\" | \"liveAndActive\";\n}>;\n\n/**\n * THE table both renderers of the cardinality predicate read: the TypeScript\n * probe ({@link file://../constraints.ts checkCardinalityConstraint}) and the\n * SQL takeover statement\n * ({@link file://../../backend/drizzle/operations/edge-claims.ts}).\n *\n * Two renderers, one table, exhaustive by type: a new cardinality cannot be\n * added without stating all three facts, and the probe and the fence cannot\n * disagree about any of them — a disagreement is exactly the shape where the\n * probe accepts a write the fence then refuses forever (or the reverse).\n */\nexport const EDGE_CARDINALITY_SPECS = {\n  one: { keyShape: \"from\", claimsWhenBornEnded: true, holderLiveness: \"live\" },\n  unique: {\n    keyShape: \"fromAndTo\",\n    claimsWhenBornEnded: true,\n    holderLiveness: \"live\",\n  },\n  oneActive: {\n    keyShape: \"from\",\n    claimsWhenBornEnded: false,\n    holderLiveness: \"liveAndActive\",\n  },\n} as const satisfies Record<ConstrainedCardinality, EdgeCardinalitySpec>;\n\n/** Every axis an edge kind's claims can sit on, for housekeeping reaps. */\nexport function edgeCardinalityAxesForKind(\n  edgeKind: string,\n): readonly string[] {\n  return Object.keys(EDGE_CARDINALITY_SPECS).map((cardinality) =>\n    edgeCardinalityAxis(cardinality, edgeKind),\n  );\n}\n\n/**\n * THE claim row one edge write reserves: its axis, and the endpoint identity\n * the axis is keyed by.\n *\n * The key is {@link encodeTupleKey}, not a delimiter join: node ids are\n * arbitrary caller data (only kind names pass `assertClaimAxisSafe`), and a\n * delimiter that a value may contain makes two different endpoint tuples\n * collapse onto one key — which would refuse a write no constraint forbids.\n */\nexport function edgeCardinalityClaimTarget(\n  params: ClaimEdgeCardinalityParams,\n): ClaimTarget {\n  const spec = EDGE_CARDINALITY_SPECS[params.cardinality];\n  return {\n    relation: \"edgeClaims\",\n    graphId: params.graphId,\n    axis: edgeCardinalityAxis(params.cardinality, params.edgeKind),\n    key:\n      spec.keyShape === \"from\" ?\n        encodeTupleKey([params.fromKind, params.fromId])\n      : encodeTupleKey([\n          params.fromKind,\n          params.fromId,\n          params.toKind,\n          params.toId,\n        ]),\n  };\n}\n\n/**\n * The fields a claim is decided from: the row's identity and the upper bound\n * that decides whether it joins an active-only population. Narrower than\n * {@link InsertEdgeParams} on purpose — a resurrect has no insert params to\n * hand over, and passing an invented `props` to satisfy a type would be a\n * fabricated value a reader has to check is unread.\n */\nexport type EdgeClaimSubject = Pick<\n  InsertEdgeParams,\n  \"graphId\" | \"id\" | \"kind\" | \"fromKind\" | \"fromId\" | \"toKind\" | \"toId\"\n> &\n  Readonly<{ validTo?: string }>;\n\n/**\n * THE claim an edge insert owes, or `undefined` when it owes none.\n *\n * `many` declares no constraint, and an `oneActive` edge born already ended\n * joins no active population — `claimsWhenBornEnded` is the one place that\n * second exemption is written down, and the probe reads the same field, so the\n * two cannot drift into \"probed but unclaimed\" (a silent hole) or \"claimed but\n * unprobed\" (a refusal with no matching error).\n */\nexport function edgeCardinalityClaim(\n  cardinality: Cardinality,\n  subject: EdgeClaimSubject,\n): ClaimEdgeCardinalityParams | undefined {\n  if (cardinality === \"many\") return undefined;\n  if (\n    !EDGE_CARDINALITY_SPECS[cardinality].claimsWhenBornEnded &&\n    subject.validTo !== undefined\n  ) {\n    return undefined;\n  }\n  return {\n    graphId: subject.graphId,\n    cardinality,\n    edgeKind: subject.kind,\n    edgeId: subject.id,\n    fromKind: subject.fromKind,\n    fromId: subject.fromId,\n    toKind: subject.toKind,\n    toId: subject.toId,\n  };\n}\n\n/**\n * The refusal a lost claim raises — built by the SAME owners the probe calls,\n * so the fence's error is `instanceof` the same class and carries the same\n * payload as the probe's for the same violation (I3).\n */\n/**\n * Builds the public cardinality refusal for a claim decision.\n *\n * Both the ordinary claim path and the fused claim-plus-edge path call this\n * owner. Keeping the translation here prevents a backend result discriminator\n * from growing a second spelling of the same typed error.\n */\nexport function edgeCardinalityClaimRefusal(\n  params: ClaimEdgeCardinalityParams,\n): CardinalityError {\n  const error =\n    params.cardinality === \"unique\" ?\n      checkUniqueEdge(\n        params.edgeKind,\n        params.fromKind,\n        params.fromId,\n        params.toKind,\n        params.toId,\n        1,\n      )\n    : checkCardinality(\n        params.edgeKind,\n        params.fromKind,\n        params.fromId,\n        params.cardinality,\n        1,\n        true,\n      );\n  // Both owners refuse for an incumbent count of one on every constrained\n  // cardinality; the fallback keeps the return type honest without inventing a\n  // second spelling of the message.\n  return (\n    error ??\n    new CardinalityError({\n      edgeKind: params.edgeKind,\n      fromKind: params.fromKind,\n      fromId: params.fromId,\n      cardinality: params.cardinality,\n      existingCount: 1,\n    })\n  );\n}\n\n/**\n * Converts the engine's \"relation does not exist\" into a typed precondition\n * error naming the relation and the way to create it.\n *\n * Checked at FIRST USE rather than at store construction: a graph with no\n * constrained edge kind never issues a claim and must not pay a catalog read\n * for a relation it will never touch. A database bootstrapped before this\n * relation existed reaches it here, on the first constrained edge write, with\n * an error that says what to run instead of an opaque driver failure.\n */\nexport function edgeClaimRelationMissing(\n  graphId: string,\n  cause: unknown,\n): ConfigurationError {\n  return new ConfigurationError(\n    \"Enforcing a declared edge cardinality needs the edge claim relation \" +\n      \"(typegraph_edge_claims), and this database does not have it. \" +\n      \"Databases initialized before this relation existed were never sent \" +\n      \"its CREATE TABLE, because the bootstrap DDL runs only on first boot.\",\n    { code: \"EDGE_CLAIM_RELATION_MISSING\", graphId },\n    {\n      cause,\n      suggestion:\n        \"Run the generated migration SQL (generatePostgresMigrationSQL / \" +\n        \"generateSqliteMigrationSQL) against this database, or declare the \" +\n        'edge kind `cardinality: \"many\"` and enforce the limit in ' +\n        \"application code.\",\n    },\n  );\n}\n\nasync function withEdgeClaimRelationPrecondition<T>(\n  graphId: string,\n  issue: () => Promise<T>,\n): Promise<T> {\n  try {\n    return await issue();\n  } catch (error) {\n    if (!isMissingTableError(error)) throw error;\n    throw edgeClaimRelationMissing(graphId, error);\n  }\n}\n\ntype ClaimModeTarget = Readonly<\n  Partial<\n    Pick<\n      GraphBackend,\n      (typeof CLAIMS)[\"core\"][number] | \"claimEdgeCardinalityGuarded\"\n    >\n  >\n>;\n\n/** The narrow backend facet an edge claim is written through. */\ntype ClaimTargetMembers = ClaimModeTarget;\n\n/**\n * The one owner of whether a single write may replace its entity probe with a\n * guarded claim. Both claim-bundle support and the optional strong member are\n * required: the member is the backend's explicit opt-in contract, while the\n * bundle verdict proves the target can actually persist claims. Either absence\n * deliberately preserves the legacy probe-then-claim protocol.\n */\nexport function edgeCardinalityClaimMode(\n  backend: ClaimModeTarget,\n  verdict: BundleVerdictOf<typeof CLAIMS>,\n):\n  | Readonly<{\n      kind: \"guarded\";\n      claim: NonNullable<GraphBackend[\"claimEdgeCardinalityGuarded\"]>;\n    }>\n  | Readonly<{\n      kind: \"probeThenClaim\";\n      support: ReturnType<typeof claimSupport>;\n    }> {\n  const support = claimSupport(backend, verdict);\n  const guardedClaim = backend.claimEdgeCardinalityGuarded;\n  return !support.supported || guardedClaim === undefined ?\n      { kind: \"probeThenClaim\", support }\n    : { kind: \"guarded\", claim: guardedClaim };\n}\n\n/**\n * Issues ONE edge cardinality claim against the object the row write goes to,\n * refusing with the declared error when a live incumbent holds the axis.\n *\n * A backend that declares no claim support writes no claim and keeps exactly\n * the fence it has today (the per-graph write lock around the probe) — a\n * declared gap with a parity-matrix row, never a silent unfencing and never a\n * new refusal.\n */\nexport async function claimEdgeCardinality(\n  backend: ClaimTargetMembers,\n  verdict: BundleVerdictOf<typeof CLAIMS>,\n  claim: ClaimEdgeCardinalityParams,\n): Promise<void> {\n  const mode = edgeCardinalityClaimMode(backend, verdict);\n  if (mode.kind === \"guarded\") {\n    const outcome = await withEdgeClaimRelationPrecondition(claim.graphId, () =>\n      mode.claim(claim),\n    );\n    if (outcome.status === \"refused\") {\n      throw edgeCardinalityClaimRefusal(claim);\n    }\n    return;\n  }\n  const support = mode.support;\n  if (!support.supported) return;\n  const outcome = await withEdgeClaimRelationPrecondition(claim.graphId, () =>\n    support.claims.claimEdgeCardinality(claim),\n  );\n  if (outcome.status === \"refused\") throw edgeCardinalityClaimRefusal(claim);\n}\n\n/**\n * Issues a batch's claims as ONE statement, entries sorted by\n * {@link compareClaimTargets}.\n *\n * One statement takes its row locks in a fixed order, so a batch cannot\n * deadlock against itself; sorting is what stops two writers of the same two\n * axes from taking them in opposite orders. In-batch duplicates are refused\n * before this by the batch validation wrapper's pending-cardinality state, so\n * the backend's duplicate-conflict-target guard stays a defensive invariant.\n */\nexport async function claimEdgeCardinalityBatch(\n  backend: ClaimTargetMembers,\n  verdict: BundleVerdictOf<typeof CLAIMS>,\n  claims: readonly ClaimEdgeCardinalityParams[],\n): Promise<void> {\n  if (claims.length === 0) return;\n  const support = claimSupport(backend, verdict);\n  if (!support.supported) return;\n  const ordered = claims\n    .map((claim) => ({ claim, target: edgeCardinalityClaimTarget(claim) }))\n    .toSorted((left, right) => compareClaimTargets(left.target, right.target));\n  const graphId = ordered[0]?.claim.graphId ?? \"\";\n  const outcomes = await withEdgeClaimRelationPrecondition(graphId, () =>\n    support.claims.claimEdgeCardinalityBatch(\n      ordered.map((entry) => entry.claim),\n    ),\n  );\n  for (const [index, outcome] of outcomes.entries()) {\n    const entry = ordered[index];\n    if (entry !== undefined && outcome.status === \"refused\") {\n      throw edgeCardinalityClaimRefusal(entry.claim);\n    }\n  }\n}\n\n/**\n * Housekeeping: drops the claim rows named edges held.\n *\n * Never a fence and never load-bearing — a claim whose holder is gone already\n * fails the takeover statement's liveness predicate, so the axis is reusable\n * whether or not this ran. It exists so a hard delete does not leave a row\n * behind forever. A backend without claim support has nothing to purge.\n */\nexport async function purgeEdgeClaims(\n  backend: ClaimTargetMembers,\n  verdict: BundleVerdictOf<typeof CLAIMS>,\n  graphId: string,\n  edgeIds: readonly string[],\n): Promise<void> {\n  if (edgeIds.length === 0) return;\n  const support = claimSupport(backend, verdict);\n  if (!support.supported) return;\n  await withEdgeClaimRelationPrecondition(graphId, () =>\n    support.claims.purgeEdgeClaims({ graphId, edgeIds }),\n  );\n}\n"]}